Refactoring to batch (#181)

The rationale of this pull request is to

* have more maintainable and granular code
* not maintain the custom made job engine
* allow the users to customize the defaults of the releaser more easy
* allow the users to create their own steps without the need to change any existing code
* allow the users to fully change the flows and tasks logic
* abstract underlying batch mechanism (Spring Batch) so it doesn't leak to production code
* allow parallelization of the release process and release tasks
This commit is contained in:
Marcin Grzejszczak
2019-12-23 13:49:58 +01:00
committed by GitHub
parent ea85372e05
commit 8992e45321
2130 changed files with 12441 additions and 6429 deletions

0
releaser-spring/.jdk8 Normal file
View File

109
releaser-spring/pom.xml Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>releaser-spring</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.cloud.internal</groupId>
<artifactId>releaser-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud.internal</groupId>
<artifactId>releaser-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${org.eclipse.jgit-version}</version>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>${jopt-simple.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>com.jakewharton.fliptables</groupId>
<artifactId>fliptables</artifactId>
<version>${fliptables.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>sonar</id>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>surefireArgLine</propertyName>
<destFile>${project.build.directory}/jacoco.exec</destFile>
</configuration>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Sets the VM argument line used when unit tests are run. -->
<argLine>${surefireArgLine}</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser;
import releaser.internal.options.Options;
import releaser.internal.options.Parser;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.spring.ExecutionResultHandler;
import releaser.internal.spring.SpringReleaser;
import org.springframework.boot.CommandLineRunner;
public class ReleaserCommandLineRunner implements CommandLineRunner {
private final SpringReleaser releaser;
private final ExecutionResultHandler executionResultHandler;
private final Parser parser;
public ReleaserCommandLineRunner(SpringReleaser releaser,
ExecutionResultHandler executionResultHandler, Parser parser) {
this.releaser = releaser;
this.executionResultHandler = executionResultHandler;
this.parser = parser;
}
@Override
public void run(String... strings) {
// TODO:
// * Check out Spring Shell - maybe move interactive stuff out of it
// * Spring Shell would spit out the options at the end
// * Check why I can't run a composite job as a batch job (transaction not
// committed exception)
Options options = this.parser.parse(strings);
ExecutionResult executionResult = this.releaser.release(options);
this.executionResultHandler.accept(executionResult);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.buildsystem;
import java.util.ArrayList;
import java.util.List;
import releaser.internal.ReleaserProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class BuildsystemConfiguration {
@Bean
@ConditionalOnMissingBean
BomParser mavenBomParser(ReleaserProperties releaserProperties,
@Autowired(required = false) List<CustomBomParser> customBomParsers) {
return new MavenBomParser(releaserProperties, customBomParsers(customBomParsers));
}
private List<CustomBomParser> customBomParsers(
List<CustomBomParser> customBomParsers) {
return customBomParsers == null ? new ArrayList<>() : customBomParsers;
}
@Bean
@ConditionalOnMissingBean
BomParser gradleBomParser(ReleaserProperties releaserProperties,
@Autowired(required = false) List<CustomBomParser> customBomParsers) {
return new GradleBomParser(releaserProperties,
customBomParsers(customBomParsers));
}
@Bean
@ConditionalOnMissingBean
ProjectPomUpdater pomUpdater(ReleaserProperties releaserProperties,
List<BomParser> bomParsers) {
return new ProjectPomUpdater(releaserProperties, bomParsers);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.options;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Marcin Grzejszczak
*/
public class Options implements Serializable {
/**
* Is meta release set.
*/
public Boolean metaRelease;
/**
* Is full release set.
*/
public Boolean fullRelease;
/**
* Is interactive mode set.
*/
public Boolean interactive;
/**
* Is dry run set.
*/
public Boolean dryRun;
/**
* List of task names to release.
*/
public List<String> taskNames;
/**
* Name of the task / projects to start from.
*/
public String startFrom = "";
/**
* Range of task / projects to release.
*/
public String range = "";
Options(Boolean metaRelease, Boolean fullRelease, Boolean interactive, Boolean dryRun,
List<String> taskNames, String startFrom, String range) {
this.metaRelease = metaRelease;
this.fullRelease = fullRelease;
this.interactive = interactive;
this.dryRun = dryRun;
this.taskNames = taskNames.stream().map(this::removeQuotingChars)
.collect(Collectors.toList());
this.startFrom = removeQuotingChars(startFrom);
this.range = removeQuotingChars(range);
}
private String removeQuotingChars(String string) {
if (string != null && string.startsWith("'") && string.endsWith("'")) {
return string.substring(1, string.length() - 1);
}
return string;
}
@Override
public String toString() {
return "Options{" + "metaRelease=" + this.metaRelease + ", fullRelease="
+ this.fullRelease + ", interactive=" + this.interactive + ", dryRun="
+ this.dryRun + ", taskNames=" + this.taskNames + ", startFrom='"
+ this.startFrom + '\'' + ", range='" + this.range + '\'' + '}';
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.options;
import java.util.ArrayList;
import java.util.List;
public class OptionsBuilder {
private Boolean metaRelease = false;
private Boolean fullRelease = false;
private Boolean interactive = true;
private Boolean dryRun = false;
private List<String> taskNames = new ArrayList<>();
private String startFrom = "";
private String range = "";
public OptionsBuilder metaRelease(Boolean metaRelease) {
this.metaRelease = metaRelease;
return this;
}
public OptionsBuilder fullRelease(Boolean fullRelease) {
this.fullRelease = fullRelease;
return this;
}
public OptionsBuilder interactive(Boolean interactive) {
this.interactive = interactive;
return this;
}
public OptionsBuilder dryRun(Boolean dryRun) {
this.dryRun = dryRun;
return this;
}
public OptionsBuilder taskNames(List<String> taskNames) {
this.taskNames = taskNames;
return this;
}
public OptionsBuilder startFrom(String startFrom) {
this.startFrom = startFrom;
return this;
}
public OptionsBuilder range(String range) {
this.range = range;
return this;
}
public Options options() {
return new Options(this.metaRelease, this.fullRelease, this.interactive,
this.dryRun, this.taskNames, this.startFrom, this.range);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.options;
/**
* Converts input arguments to a {@link Options}.
*
* @author Marcin Grzejszczak
*/
public interface Parser {
Options parse(String[] args);
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.sagan;
import java.util.List;
import releaser.internal.ReleaserProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
/**
* @author Marcin Grzejszczak
*/
@Configuration
class SaganConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "releaser.sagan.update-sagan", matchIfMissing = true)
SaganClient saganClient(ReleaserProperties properties) {
RestTemplate restTemplate = restTemplate(properties);
return new RestTemplateSaganClient(restTemplate, properties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "releaser.sagan.update-sagan", havingValue = "false")
SaganClient noOpSaganClient() {
return new SaganClient() {
@Override
public Project getProject(String projectName) {
return null;
}
@Override
public Release getRelease(String projectName, String releaseVersion) {
return null;
}
@Override
public Release deleteRelease(String projectName, String releaseVersion) {
return null;
}
@Override
public Project updateRelease(String projectName,
List<ReleaseUpdate> releaseUpdate) {
return null;
}
@Override
public Project patchProject(Project project) {
return null;
}
};
}
private RestTemplate restTemplate(ReleaserProperties properties) {
Assert.hasText(properties.getGit().getOauthToken(),
"In order to connect to Sagan you need to pass the Github OAuth token. "
+ "You can do it via the [--releaser.git.oauth-token=...] "
+ "command line argument or an env variable [export RELEASER_GIT_OAUTH_TOKEN=...].");
return new RestTemplateBuilder()
.basicAuthentication(properties.getGit().getOauthToken(), "").build();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.File;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.project.ProcessedProject;
import releaser.internal.project.ProjectVersion;
import releaser.internal.project.Projects;
/**
* Arguments for a task.
*/
public final class Arguments implements Serializable {
/**
* Cloned location of the project.
*/
public final File project;
/**
* All projects taken from the BOM.
*/
public final Projects projects;
/**
* Original version of this project before any manipulations.
*/
public final ProjectVersion originalVersion;
/**
* Version of this project from the BOM.
*/
public final ProjectVersion versionFromBom;
/**
* Releaser properties updated for this project.
*/
public final ReleaserProperties properties;
/**
* Options updated for this project.
*/
public final Options options;
/**
* Current project to run.
*/
public final ProjectToRun projectToRun;
/**
* List of processed projects.
*/
public final List<ProcessedProject> processedProjects;
private Arguments(ProjectToRun thisProject, Projects projects,
ProjectVersion currentProjectFromBom) {
this.project = thisProject.thisProjectFolder;
this.projects = projects;
this.originalVersion = thisProject.originalVersion;
this.versionFromBom = currentProjectFromBom;
this.properties = thisProject.thisProjectReleaserProperties;
this.options = thisProject.options;
this.projectToRun = thisProject;
this.processedProjects = new LinkedList<>(Collections.singletonList(
new ProcessedProject(thisProject.thisProjectReleaserProperties,
this.versionFromBom, this.originalVersion)));
}
// in this case the project will be the BOM
private Arguments(ProjectToRun thisProject,
List<ProcessedProject> processedProjects) {
this.project = thisProject.thisProjectFolder;
this.projects = new Projects(processedProjects.stream()
.map(p -> p.newProjectVersion).collect(Collectors.toSet()));
this.originalVersion = thisProject.originalVersion;
this.versionFromBom = thisProject.thisProjectVersionFromBom;
this.properties = thisProject.thisProjectReleaserProperties;
this.options = thisProject.options;
this.projectToRun = thisProject;
this.processedProjects = processedProjects;
}
public static Arguments forProject(ProjectToRun thisProject) {
return new Arguments(thisProject,
thisProject.allProjectsFromBom.allProjectVersionsFromBom,
thisProject.allProjectsFromBom.currentProjectFromBom);
}
public static Arguments forPostRelease(ReleaserProperties properties,
ProjectsToRun projectsToRun) {
List<ProjectToRun> projects = projectsToRun.stream()
.map(ProjectToRun.ProjectToRunSupplier::get)
.collect(Collectors.toCollection(LinkedList::new));
List<ProcessedProject> processedProjects = projectsToRunToProcessedProject(
projects);
ProjectToRun releaseTrainProject = projects.stream()
.filter(p -> isReleaseTrainProject(properties, p)).findFirst()
.orElseThrow(
() -> new IllegalStateException("Missing release train version"));
return new Arguments(releaseTrainProject, processedProjects);
}
private static boolean isReleaseTrainProject(ReleaserProperties properties,
ProjectToRun p) {
return p.originalVersion.projectName
.equals(properties.getMetaRelease().getReleaseTrainProjectName())
|| properties.getMetaRelease().getReleaseTrainDependencyNames()
.contains(p.originalVersion.projectName)
|| p.name()
.equals(properties.getMetaRelease().getReleaseTrainProjectName());
}
private static List<ProcessedProject> projectsToRunToProcessedProject(
List<ProjectToRun> projects) {
return projects.stream()
.map(p -> new ProcessedProject(p.thisProjectReleaserProperties,
p.thisProjectVersionFromBom, p.originalVersion))
.collect(Collectors.toCollection(LinkedList::new));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import javax.sql.DataSource;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import releaser.internal.ReleaserProperties;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.Jackson2ExecutionContextStringSerializer;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EnableBatchProcessing
class BatchConfiguration {
@Bean
TaskExecutor batchTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.initialize();
return executor;
}
@Bean
@ConditionalOnMissingBean
ExecutionResultHandler springBatchExecutionResultHandler(JobExplorer jobExplorer,
ConfigurableApplicationContext context) {
return new SpringBatchExecutionResultHandler(jobExplorer, context);
}
@Bean
@ConditionalOnMissingBean
FlowRunnerTaskExecutorSupplier defaultFlowRunnerTaskExecutorSupplier() {
return new FlowRunnerTaskExecutorSupplier() {
};
}
@Bean
@ConditionalOnMissingBean
FlowRunner flowRunner(StepBuilderFactory stepBuilderFactory,
JobBuilderFactory jobBuilderFactory,
ProjectsToRunFactory projectsToRunFactory, JobLauncher jobLauncher,
FlowRunnerTaskExecutorSupplier flowRunnerTaskExecutorSupplier,
ConfigurableApplicationContext context,
ReleaserProperties releaserProperties) {
return new SpringBatchFlowRunner(stepBuilderFactory, jobBuilderFactory,
projectsToRunFactory, jobLauncher, flowRunnerTaskExecutorSupplier,
context, releaserProperties);
}
@Bean
Jackson2ExecutionContextStringSerializer myJackson2ExecutionContextStringSerializer() {
Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
// Needed to add this to serialize the exceptions
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.enableDefaultTyping();
serializer.setObjectMapper(objectMapper);
return serializer;
}
// Needed to add this to serialize the exceptions
@Bean
BatchConfigurer myBatchConfigurer(DataSource dataSource,
Jackson2ExecutionContextStringSerializer myJackson2ExecutionContextStringSerializer,
PlatformTransactionManager transactionManager) {
return new DefaultBatchConfigurer(dataSource) {
@Override
protected JobExplorer createJobExplorer() throws Exception {
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(dataSource);
jobExplorerFactoryBean
.setSerializer(myJackson2ExecutionContextStringSerializer);
jobExplorerFactoryBean.afterPropertiesSet();
return jobExplorerFactoryBean.getObject();
}
@Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean
.setSerializer(myJackson2ExecutionContextStringSerializer);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.afterPropertiesSet();
return jobRepositoryFactoryBean.getObject();
}
};
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.options.OptionsBuilder;
/**
* Releaser that gets input from console.
*
* @author Marcin Grzejszczak
*/
class DefaultSpringReleaser implements SpringReleaser {
private final ReleaserProperties properties;
private final OptionsAndPropertiesFactory optionsAndPropertiesFactory;
private final ProjectsToRunFactory projectsToRunFactory;
private final TasksToRunFactory tasksToRunFactory;
private final FlowRunner flowRunner;
DefaultSpringReleaser(ReleaserProperties properties,
OptionsAndPropertiesFactory optionsAndPropertiesFactory,
ProjectsToRunFactory projectsToRunFactory,
TasksToRunFactory tasksToRunFactory, FlowRunner flowRunner) {
this.properties = properties;
this.optionsAndPropertiesFactory = optionsAndPropertiesFactory;
this.projectsToRunFactory = projectsToRunFactory;
this.tasksToRunFactory = tasksToRunFactory;
this.flowRunner = flowRunner;
}
/**
* Default behaviour - interactive mode.
*/
@Override
public ExecutionResult release() {
return release(new OptionsBuilder().options());
}
@Override
public ExecutionResult release(Options options) {
OptionsAndProperties optionsAndProperties = prepareOptionsAndProperties(options,
this.properties);
// order matters! Tasks will mutate options and properties
TasksToRun releaseTasksToRun = releaseTasksFromOptions(optionsAndProperties);
ProjectsToRun projectsToRun = releaseProjects(optionsAndProperties);
ExecutionResult releaseTasksExecutionResult = runReleaseTasks(
optionsAndProperties, projectsToRun, releaseTasksToRun);
TasksToRun postReleaseTrainTasksToRun = postReleaseTrainTasksFromOptions(
optionsAndProperties);
ExecutionResult postReleaseTrainTasksExecutionResult = runPostReleaseTasks(
optionsAndProperties, postReleaseTrainTasksToRun);
return releaseTasksExecutionResult.merge(postReleaseTrainTasksExecutionResult);
}
private OptionsAndProperties prepareOptionsAndProperties(Options options,
ReleaserProperties properties) {
return this.optionsAndPropertiesFactory.get(properties, options);
}
private ProjectsToRun releaseProjects(OptionsAndProperties options) {
return this.projectsToRunFactory.release(options);
}
private TasksToRun releaseTasksFromOptions(OptionsAndProperties options) {
return this.tasksToRunFactory.release(options);
}
private TasksToRun postReleaseTrainTasksFromOptions(
OptionsAndProperties optionsAndProperties) {
return this.tasksToRunFactory.postRelease(optionsAndProperties.options);
}
private ExecutionResult runReleaseTasks(OptionsAndProperties optionsAndProperties,
ProjectsToRun projectsToRun, TasksToRun tasksToRun) {
return this.flowRunner.runReleaseTasks(optionsAndProperties.options,
optionsAndProperties.properties, projectsToRun, tasksToRun);
}
private ExecutionResult runPostReleaseTasks(OptionsAndProperties optionsAndProperties,
TasksToRun postReleaseTasksToRun) {
return this.flowRunner.runPostReleaseTrainTasks(optionsAndProperties.options,
optionsAndProperties.properties, "postRelease", postReleaseTasksToRun);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import releaser.internal.tech.BuildUnstableException;
/**
* Task execution result. Contains a list of exceptions thrown while running the task.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class ExecutionResult implements Serializable {
private List<Exception> exceptions = new LinkedList<>();
public ExecutionResult() {
}
public ExecutionResult(Exception throwable) {
this.exceptions.add(throwable);
}
public ExecutionResult(List<Exception> throwables) {
this.exceptions.addAll(throwables);
}
public static ExecutionResult success() {
return new ExecutionResult();
}
public static ExecutionResult failure(Exception throwable) {
return new ExecutionResult(throwable);
}
public static ExecutionResult unstable(Exception ex) {
return new ExecutionResult(ex instanceof BuildUnstableException ? ex
: new BuildUnstableException(ex));
}
public RuntimeException foundExceptions() {
if (this.exceptions.isEmpty()) {
return null;
}
if (this.exceptions.size() == 1) {
Throwable throwable = this.exceptions.get(0);
return throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable);
}
if (isUnstable()) {
return new MergedUnstableThrowable(this.exceptions);
}
return new MergedThrowable(this.exceptions);
}
public ExecutionResult merge(ExecutionResult other) {
ExecutionResult merged = new ExecutionResult(this.exceptions);
merged.exceptions.addAll(other.exceptions);
return merged;
}
public boolean isUnstable() {
return !this.exceptions.isEmpty() && this.exceptions.stream()
.allMatch(t -> t instanceof BuildUnstableException);
}
public boolean isFailure() {
return !this.exceptions.isEmpty() && this.exceptions.stream()
.anyMatch(t -> !(t instanceof BuildUnstableException));
}
public boolean isSuccess() {
return this.exceptions.isEmpty();
}
public boolean isFailureOrUnstable() {
return !this.exceptions.isEmpty();
}
public List<Exception> getExceptions() {
return this.exceptions;
}
public void setExceptions(List<Exception> exceptions) {
this.exceptions = exceptions;
}
private static final class MergedThrowable extends RuntimeException
implements Serializable {
private MergedThrowable(List<Exception> throwables) {
super("Failed due to the following exceptions " + throwables);
}
}
private static final class MergedUnstableThrowable extends BuildUnstableException
implements Serializable {
private MergedUnstableThrowable(List<Exception> throwables) {
super("Unstable due to the following exceptions " + throwables);
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.function.Consumer;
/**
* Handles the result of the execution of the train or project release. Example: You can
* print the results in a table, throw exceptions when the build was faulty or just log
* them when it was unstable.
*/
public interface ExecutionResultHandler extends Consumer<ExecutionResult> {
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.List;
import releaser.internal.tasks.ReleaserTask;
/**
* A report from running a task. Can be mapped to a row in a table for a single execution
* of a release task.
*/
public class ExecutionResultReport {
private String projectName;
private String shortName;
private String description;
private Class<? extends ReleaserTask> releaserTaskType;
private String state;
private List<Throwable> exceptions;
ExecutionResultReport() {
}
public ExecutionResultReport(String projectName, String shortName, String description,
Class<? extends ReleaserTask> releaserTaskType, String state,
List<Throwable> exceptions) {
this.projectName = projectName;
this.shortName = shortName;
this.description = description;
this.releaserTaskType = releaserTaskType;
this.state = state;
this.exceptions = exceptions;
}
public String getProjectName() {
return this.projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getShortName() {
return this.shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Class<? extends ReleaserTask> getReleaserTaskType() {
return this.releaserTaskType;
}
public void setReleaserTaskType(Class<? extends ReleaserTask> releaserTaskType) {
this.releaserTaskType = releaserTaskType;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
public List<Throwable> getExceptions() {
return this.exceptions;
}
public void setExceptions(List<Throwable> exceptions) {
this.exceptions = exceptions;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.tasks.ReleaserTask;
/**
* Knows how to run a flow of tasks.
*/
public interface FlowRunner {
/**
* Decide what to do before running a task.
* @param options - options prepared for the task
* @param properties - specific releaser properties for this task
* @param releaserTask - the task to run
* @return the decision on what to do with the task
*/
default Decision beforeTask(Options options, ReleaserProperties properties,
ReleaserTask releaserTask) {
return Decision.CONTINUE;
}
/**
* Decide what to do after running a task.
* @param options - options prepared for the task
* @param properties - specific releaser properties for this task
* @param releaserTask - the task to run
* @return the decision on what to do with the task
*/
default Decision afterTask(Options options, ReleaserProperties properties,
ReleaserTask releaserTask) {
return Decision.CONTINUE;
}
/**
* Runs the release tasks.
* @param options - options coming from the input
* @param properties - releaser properties coming from the input
* @param projectToRuns - set of projects to run (for a single release it's simple)
* @param tasksToRun - set of release tasks to run for each project
* @return the result of release execution
*/
ExecutionResult runReleaseTasks(Options options, ReleaserProperties properties,
ProjectsToRun projectToRuns, TasksToRun tasksToRun);
/**
* Runs the post release tasks.
* @param options - options coming from the input
* @param properties - releaser properties coming from the input
* @param executingTaskName - name for the post release task
* @param tasksToRun - set of post release tasks to run for each project
* @return the result of release execution
*/
ExecutionResult runPostReleaseTrainTasks(Options options,
ReleaserProperties properties, String executingTaskName,
TasksToRun tasksToRun);
/**
* Decision to be taken before and after running a task.
*/
enum Decision {
/**
* Allows to continue to the next task.
*/
CONTINUE,
/**
* Skips the current task and goes to the next one.
*/
SKIP,
/**
* Aborts the execution of the whole release.
*/
ABORT
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.function.Supplier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* Customizes the {@link TaskExecutor} for the flow runner.
*/
public interface FlowRunnerTaskExecutorSupplier extends Supplier<TaskExecutor> {
@Override
default TaskExecutor get() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
class OptionsAndProperties {
final ReleaserProperties properties;
final Options options;
OptionsAndProperties(ReleaserProperties properties, Options options) {
this.properties = properties;
this.options = options;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
class OptionsAndPropertiesFactory {
OptionsAndProperties get(ReleaserProperties properties, Options options) {
if (options.metaRelease) {
properties.getGit().setFetchVersionsFromGit(false);
properties.getMetaRelease().setEnabled(true);
options.fullRelease = true;
}
return new OptionsAndProperties(properties, options);
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.options.Options;
import releaser.internal.options.OptionsBuilder;
import releaser.internal.options.Parser;
import releaser.internal.tasks.ReleaserTask;
import releaser.internal.tasks.SingleProjectReleaserTask;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;
/**
* @author Marcin Grzejszczak
*/
class OptionsParser implements Parser {
private static final Logger log = LoggerFactory.getLogger(OptionsParser.class);
private final List<ReleaserTask> allTasks;
private final List<SingleProjectReleaserTask> singleProjectReleaserTasks;
private final ConfigurableApplicationContext context;
OptionsParser(List<ReleaserTask> allTasks,
List<SingleProjectReleaserTask> singleProjectReleaserTasks,
ConfigurableApplicationContext context) {
this.allTasks = allTasks;
this.singleProjectReleaserTasks = singleProjectReleaserTasks;
this.context = context;
}
@Override
public Options parse(String[] args) {
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
log.info("Got following args <{}>", args);
try {
ArgumentAcceptingOptionSpec<Boolean> metaReleaseOpt = parser
.acceptsAll(Arrays.asList("x", "meta-release"),
"Do you want to do the meta release?")
.withRequiredArg().ofType(Boolean.class).defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> fullReleaseOpt = parser
.acceptsAll(Arrays.asList("f", "full-release"),
"Do you want to do the full release of a single project?")
.withOptionalArg().ofType(Boolean.class).defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> interactiveOpt = parser.acceptsAll(
Arrays.asList("i", "interactive"),
"Do you want to set the properties from the command line of a single project?")
.withRequiredArg().ofType(Boolean.class).defaultsTo(true);
ArgumentAcceptingOptionSpec<Boolean> dryRunOpt = parser.acceptsAll(
Arrays.asList("dr", "dry-run"),
"Do you want to do the release / meta release with build and install projects locally only?")
.withRequiredArg().ofType(Boolean.class).defaultsTo(false);
LinkedList<ReleaserTask> singleProjectReleaseTasks = this.allTasks.stream()
.filter(releaserTask -> releaserTask instanceof SingleProjectReleaserTask)
.collect(Collectors.toCollection(LinkedList::new));
singleProjectReleaseTasks.forEach(task -> parser
.acceptsAll(Arrays.asList(task.shortName(), task.name()),
task.description())
.withOptionalArg());
ArgumentAcceptingOptionSpec<String> startFromOpt = parser.acceptsAll(
Arrays.asList("a", "start-from"),
"Starts all release task starting "
+ "from the given task. Requires passing the task name (either one letter or the full name)")
.withRequiredArg().ofType(String.class);
ArgumentAcceptingOptionSpec<String> taskNamesOpt = parser
.acceptsAll(Arrays.asList("tn", "task-names"),
"Starts all release task for the given task names")
.withRequiredArg().ofType(String.class).defaultsTo("");
ArgumentAcceptingOptionSpec<String> rangeOpt = parser.acceptsAll(
Arrays.asList("r", "range"),
"Runs release tasks from the given range. Requires passing "
+ "the task names with a hyphen. The first task is inclusive, "
+ "the second inclusive. E.g. 's-m' would mean running 'snapshot', "
+ "'push' and 'milestone' tasks")
.withRequiredArg().ofType(String.class);
parser.acceptsAll(Arrays.asList("h", "help")).withOptionalArg();
OptionSet options = parser.parse(args);
if (options.has("h")) {
printHelpMessage(parser);
SpringApplication.exit(this.context, () -> 0);
System.exit(0);
}
Boolean metaRelease = options.valueOf(metaReleaseOpt);
Boolean interactive = options.valueOf(interactiveOpt);
Boolean dryRun = options.valueOf(dryRunOpt);
Boolean fullRelease = options.has(fullReleaseOpt);
List<String> providedTaskNames = StringUtils.hasText(options
.valueOf(taskNamesOpt)) ? Arrays.asList(
removeQuotingChars(options.valueOf(taskNamesOpt)).split(","))
: new ArrayList<>();
providedTaskNames = providedTaskNames.stream().map(this::removeQuotingChars)
.collect(Collectors.toList());
log.info("Passed tasks {} from command line", providedTaskNames);
List<String> allTaskNames = singleProjectReleaseTasks.stream()
.map(ReleaserTask::name).collect(Collectors.toList());
List<String> tasksFromOptions = singleProjectReleaseTasks.stream().filter(
task -> options.has(task.name()) || options.has(task.shortName()))
.map(ReleaserTask::name).collect(Collectors.toList());
if (providedTaskNames.isEmpty()) {
providedTaskNames.addAll(tasksFromOptions.isEmpty() && !metaRelease
? allTaskNames : tasksFromOptions);
}
List<String> taskNames = filterProvidedTaskNames(providedTaskNames,
allTaskNames, metaRelease);
String startFrom = options.valueOf(startFromOpt);
String range = options.valueOf(rangeOpt);
Options buildOptions = new OptionsBuilder().metaRelease(metaRelease)
.fullRelease(fullRelease).interactive(interactive).dryRun(dryRun)
.taskNames(taskNames).startFrom(startFrom).range(range).options();
log.info(
"\n\nWill use the following options to process the project\n\n{}\n\n",
buildOptions);
return buildOptions;
}
catch (Exception e) {
printErrorMessage(e, parser);
throw e;
}
}
List<String> filterProvidedTaskNames(List<String> providedTaskNames,
List<String> allTaskNames, boolean metaRelease) {
if (metaRelease) {
return providedTaskNames;
}
return allTaskNames.stream().filter(providedTaskNames::contains)
.collect(Collectors.toCollection(LinkedList::new));
}
private String removeQuotingChars(String string) {
if (string.startsWith("'") && string.endsWith("'")) {
return string.substring(1, string.length() - 1);
}
return string;
}
private void printErrorMessage(Exception e, OptionParser parser) {
System.err.println("Following exception has occurred: ");
System.err.println(e.getMessage());
e.printStackTrace();
System.err.println(intro());
System.err.println(
"java -jar releaser-spring-1.0.0.BUILD-SNAPSHOT.jar [options...] ");
try {
parser.printHelpOn(System.err);
}
catch (IOException e1) {
throw new IllegalStateException(e1);
}
System.err.println(examples());
}
private void printHelpMessage(OptionParser parser) {
try {
System.out.println(intro());
parser.printHelpOn(System.out);
System.out.println(examples());
}
catch (IOException e1) {
throw new IllegalStateException(e1);
}
}
private String intro() {
return "\nHere you can find the list of tasks in order for a single project\n\n["
+ this.singleProjectReleaserTasks.stream().map(ReleaserTask::name)
.collect(Collectors.joining(","))
+ "]\n\n";
}
private String examples() {
return "\nExamples of usage:\n\n" + "Run 'build' & 'commit' & 'deploy'\n"
+ "java -jar jar.jar -b -c -d\n\n" + "Start from 'push'\n"
+ "java -jar releaser.jar -a push\n\n" + "Range 'docs' -> 'push'\n"
+ "java -jar releaser.jar -r o-p\n\n" + "\n\n";
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.Closeable;
import java.io.File;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.project.ProjectVersion;
/**
* A single project to be released. Contains all the information necessary to release a
* project.
*/
public class ProjectToRun implements Serializable {
/**
* Cloned location of this project.
*/
public final File thisProjectFolder;
/**
* All projects taken from the BOM.
*/
public final ProjectsFromBom allProjectsFromBom;
/**
* The original version of the project.
*/
public final ProjectVersion originalVersion;
/**
* The project version of this project taken from the BOM.
*/
public final ProjectVersion thisProjectVersionFromBom;
/**
* {@link ReleaserProperties} updated for this project.
*/
public final ReleaserProperties thisProjectReleaserProperties;
/**
* {@link Options} updated for this project.
*/
public final Options options;
public ProjectToRun(File thisProjectFolder, ProjectsFromBom allProjectsFromBom,
ProjectVersion originalVersion,
ReleaserProperties thisProjectReleaserProperties, Options options) {
this.thisProjectFolder = thisProjectFolder;
this.allProjectsFromBom = allProjectsFromBom;
this.originalVersion = originalVersion;
this.thisProjectReleaserProperties = thisProjectReleaserProperties;
this.options = options;
this.thisProjectVersionFromBom = allProjectsFromBom.currentProjectFromBom;
}
public String name() {
return this.thisProjectFolder.getName();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProjectToRun that = (ProjectToRun) o;
return Objects.equals(originalVersion, that.originalVersion);
}
@Override
public int hashCode() {
return Objects.hash(originalVersion);
}
/**
* Supplier of a project to run. Since retrieval of a project may require cloning it,
* we will cache the cloned location instead of cloning it each time.
*/
public static class ProjectToRunSupplier
implements Supplier<ProjectToRun>, Closeable {
private static final Map<String, ProjectToRun> CACHE = new ConcurrentHashMap<>();
private final String projectName;
private final Supplier<ProjectToRun> project;
public ProjectToRunSupplier(String projectName, Supplier<ProjectToRun> project) {
this.projectName = projectName;
this.project = project;
}
@Override
public ProjectToRun get() {
return CACHE.computeIfAbsent(this.projectName, s -> this.project.get());
}
public String projectName() {
return this.projectName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProjectToRunSupplier that = (ProjectToRunSupplier) o;
return Objects.equals(this.projectName, that.projectName);
}
@Override
public int hashCode() {
return Objects.hash(this.projectName);
}
@Override
public void close() {
CACHE.clear();
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.project.ProjectVersion;
import releaser.internal.project.Projects;
/**
* Class representing this and other projects retrieved from the BOM.
*/
public class ProjectsFromBom {
/**
* All projects with versions taken from BOM.
*/
public final Projects allProjectVersionsFromBom;
/**
* Current project retrieved from BOM.
*/
public final ProjectVersion currentProjectFromBom;
public ProjectsFromBom(Projects allProjectVersionsFromBom,
ProjectVersion currentProjectFromBom) {
this.allProjectVersionsFromBom = allProjectVersionsFromBom;
this.currentProjectFromBom = currentProjectFromBom;
}
public ProjectsFromBom(Projects allProjectVersionsFromBom) {
this(allProjectVersionsFromBom, null);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.LinkedList;
/**
* List of projects to run. They will be fetched in a lazy fashion.
*/
public class ProjectsToRun extends LinkedList<ProjectToRun.ProjectToRunSupplier> {
public ProjectsToRun() {
}
public ProjectsToRun(ProjectToRun.ProjectToRunSupplier projectToRun) {
add(projectToRun);
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.Releaser;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.project.ProjectVersion;
import org.springframework.util.StringUtils;
class ProjectsToRunFactory implements Closeable {
private static final Logger log = LoggerFactory.getLogger(ProjectsToRunFactory.class);
private final ProjectsToRun storedProjects = new ProjectsToRun();
private final VersionsToBumpFactory versionsToBumpFactory;
private final Releaser releaser;
private final ReleaserPropertiesUpdater updater;
ProjectsToRunFactory(VersionsToBumpFactory versionsToBumpFactory, Releaser releaser,
ReleaserPropertiesUpdater updater) {
this.versionsToBumpFactory = versionsToBumpFactory;
this.releaser = releaser;
this.updater = updater;
}
ProjectsToRun release(OptionsAndProperties optionsAndProperties) {
ProjectsToRun projectsToRun = doRelease(optionsAndProperties);
this.storedProjects.addAll(projectsToRun);
return projectsToRun;
}
private ProjectsToRun doRelease(OptionsAndProperties optionsAndProperties) {
Options options = optionsAndProperties.options;
ReleaserProperties properties = optionsAndProperties.properties;
if (!options.metaRelease) {
log.info(
"Single project release picked. Will release only the current project");
File projectFolder = projectFolder(properties);
ProjectVersion version = new ProjectVersion(projectFolder);
ProjectsFromBom projectsFromBom = this.versionsToBumpFactory
.withProject(projectFolder);
return new ProjectsToRun(new ProjectToRun.ProjectToRunSupplier(
version.projectName, () -> new ProjectToRun(projectFolder,
projectsFromBom, version, properties, options)));
}
log.info("Meta release picked");
return metaReleaseProjectsToRun(options, properties);
}
ProjectsToRun postReleaseTrain(OptionsAndProperties optionsAndProperties) {
ProjectsToRun projectsToRun = doPostRelease(optionsAndProperties);
this.storedProjects.addAll(projectsToRun);
return projectsToRun;
}
private ProjectsToRun doPostRelease(OptionsAndProperties optionsAndProperties) {
Options options = optionsAndProperties.options;
ReleaserProperties properties = optionsAndProperties.properties;
if (!options.metaRelease) {
return new ProjectsToRun();
}
log.info("Meta release picked");
return metaReleasePostReleaseProjectsToRun(options, properties);
}
private File projectFolder(ReleaserProperties properties) {
String workingDir = properties.getWorkingDir();
return new File(workingDir);
}
private ProjectsToRun metaReleaseProjectsToRun(Options options,
ReleaserProperties originalProps) {
return metaProjectsToRun(options, originalProps,
metaReleaseProjects(options, originalProps));
}
private ProjectsToRun metaReleasePostReleaseProjectsToRun(Options options,
ReleaserProperties originalProps) {
return metaProjectsToRun(options, originalProps,
allButSkippedProjects(originalProps));
}
private ProjectsToRun metaProjectsToRun(Options options,
ReleaserProperties originalProps, List<String> projectNames) {
return projectNames.stream()
.map(project -> projectSupplier(options, originalProps, project))
.collect(Collectors.toCollection(ProjectsToRun::new));
}
private ProjectToRun.ProjectToRunSupplier projectSupplier(Options options,
ReleaserProperties originalProps, String project) {
return new ProjectToRun.ProjectToRunSupplier(project, () -> {
File clonedProjectFromOrg = this.releaser.clonedProjectFromOrg(project);
ReleaserProperties properties = updatePropertiesIfCustomConfigPresent(
originalProps.copy(), clonedProjectFromOrg);
log.info("Successfully cloned the project [{}] to [{}]", project,
clonedProjectFromOrg);
ProjectVersion originalVersion = new ProjectVersion(clonedProjectFromOrg);
ProjectsFromBom projectsFromBom = this.versionsToBumpFactory
.withProject(clonedProjectFromOrg);
return new ProjectToRun(clonedProjectFromOrg, projectsFromBom,
originalVersion, properties, options);
});
}
private ReleaserProperties updatePropertiesIfCustomConfigPresent(
ReleaserProperties copy, File clonedProjectFromOrg) {
return this.updater.updateProperties(copy, clonedProjectFromOrg);
}
private List<String> metaReleaseProjects(Options options,
ReleaserProperties properties) {
List<String> filteredProjects = allButSkippedProjects(properties);
if (StringUtils.hasText(options.startFrom)) {
filteredProjects = filterStartFrom(options, filteredProjects);
}
else if (!options.taskNames.isEmpty()) {
filteredProjects = filterTaskNames(options, filteredProjects);
}
log.info("\n\n\nFor meta-release, will release the projects {}\n\n\n",
filteredProjects);
return filteredProjects;
}
private List<String> allButSkippedProjects(ReleaserProperties properties) {
List<String> projects = new ArrayList<>(properties.getFixedVersions().keySet());
log.info("List of projects that should not be cloned {}",
properties.getMetaRelease().getProjectsToSkip());
List<String> filteredProjects = filterProjectsToSkip(projects, properties);
log.info("List of all projects to clone before filtering {}", filteredProjects);
return filteredProjects;
}
private List<String> allProjects(ReleaserProperties properties) {
return new ArrayList<>(properties.getFixedVersions().keySet());
}
private List<String> filterProjectsToSkip(List<String> projects,
ReleaserProperties properties) {
return projects
.stream().filter(project -> !properties.getMetaRelease()
.getProjectsToSkip().contains(project))
.collect(Collectors.toList());
}
private List<String> filterStartFrom(Options options, List<String> filteredProjects) {
log.info("Start from option provided [{}]", options.startFrom);
int projectIndex = filteredProjects.indexOf(options.startFrom);
if (projectIndex < 0) {
throw new IllegalStateException(
"Project [" + options.startFrom + "] not found");
}
if (log.isDebugEnabled()) {
log.debug("Index of project [{}] is [{}]", options.startFrom, projectIndex);
}
filteredProjects = filteredProjects.subList(projectIndex,
filteredProjects.size());
options.startFrom = "";
return filteredProjects;
}
private List<String> filterTaskNames(Options options, List<String> filteredProjects) {
log.info("Task names provided {}", options.taskNames);
filteredProjects = filteredProjects.stream()
.filter(project -> options.taskNames.contains(project))
.collect(Collectors.toList());
options.taskNames = new ArrayList<>();
return filteredProjects;
}
@Override
public void close() throws IOException {
this.storedProjects.forEach(ProjectToRun.ProjectToRunSupplier::close);
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.ArrayList;
import java.util.List;
import releaser.internal.Releaser;
import releaser.internal.ReleaserProperties;
import releaser.internal.buildsystem.GradleUpdater;
import releaser.internal.buildsystem.ProjectPomUpdater;
import releaser.internal.docs.CustomProjectDocumentationUpdater;
import releaser.internal.docs.DocumentationUpdater;
import releaser.internal.git.ProjectGitHandler;
import releaser.internal.github.CustomGithubIssues;
import releaser.internal.github.ProjectGitHubHandler;
import releaser.internal.options.Parser;
import releaser.internal.postrelease.PostReleaseActions;
import releaser.internal.project.ProjectCommandExecutor;
import releaser.internal.sagan.SaganClient;
import releaser.internal.sagan.SaganUpdater;
import releaser.internal.tasks.ReleaserTask;
import releaser.internal.tasks.SingleProjectReleaserTask;
import releaser.internal.template.TemplateGenerator;
import releaser.internal.versions.VersionsFetcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(ReleaserProperties.class)
class ReleaserConfiguration {
@Bean
@ConditionalOnMissingBean
OptionsAndPropertiesFactory optionsAndPropertiesFactory() {
return new OptionsAndPropertiesFactory();
}
@Bean
@ConditionalOnMissingBean
VersionsToBumpFactory versionsToBumpFactory(Releaser releaser,
ReleaserProperties properties) {
return new VersionsToBumpFactory(releaser, properties);
}
@Bean
@ConditionalOnMissingBean
ProjectsToRunFactory projectsToRunFactory(VersionsToBumpFactory versionsToBumpFactory,
Releaser releaser, ReleaserPropertiesUpdater updater) {
return new ProjectsToRunFactory(versionsToBumpFactory, releaser, updater);
}
@Bean
@ConditionalOnMissingBean
TasksToRunFactory tasksToRunFactory(ApplicationContext context) {
return new TasksToRunFactory(context);
}
@Bean
@ConditionalOnMissingBean
SpringReleaser springReleaser(OptionsAndPropertiesFactory optionsAndPropertiesFactory,
ProjectsToRunFactory projectsToRunFactory,
TasksToRunFactory tasksToRunFactory, FlowRunner flowRunner,
ReleaserProperties properties) {
return new DefaultSpringReleaser(properties, optionsAndPropertiesFactory,
projectsToRunFactory, tasksToRunFactory, flowRunner);
}
@Bean
@ConditionalOnMissingBean
ProjectCommandExecutor projectBuilder(ReleaserProperties properties) {
return new ProjectCommandExecutor(properties);
}
@Bean
@ConditionalOnMissingBean
VersionsFetcher versionsFetcher(ProjectPomUpdater updater,
ReleaserProperties properties) {
return new VersionsFetcher(properties, updater);
}
@Bean
@ConditionalOnMissingBean
ProjectGitHandler projectGitHandler(ReleaserProperties properties) {
return new ProjectGitHandler(properties);
}
@Bean
@ConditionalOnMissingBean
ProjectGitHubHandler projectGitHubHandler(
@Autowired(required = false) List<CustomGithubIssues> customGithubIssues,
ReleaserProperties properties) {
return new ProjectGitHubHandler(properties,
customGithubIssues != null ? customGithubIssues : new ArrayList<>());
}
@Bean
@ConditionalOnMissingBean
TemplateGenerator templateGenerator(ProjectGitHubHandler handler,
ReleaserProperties properties) {
return new TemplateGenerator(properties, handler);
}
@Bean
@ConditionalOnMissingBean
GradleUpdater gradleUpdater(ReleaserProperties properties) {
return new GradleUpdater(properties);
}
@Bean
@ConditionalOnMissingBean
SaganUpdater saganUpdater(SaganClient saganClient,
ReleaserProperties releaserProperties) {
return new SaganUpdater(saganClient, releaserProperties);
}
@Bean
@ConditionalOnMissingBean
PostReleaseActions postReleaseActions(ProjectGitHandler handler,
ProjectPomUpdater pomUpdater, GradleUpdater gradleUpdater,
ProjectCommandExecutor projectCommandExecutor,
ReleaserProperties releaserProperties, VersionsFetcher versionsFetcher) {
return new PostReleaseActions(handler, pomUpdater, gradleUpdater,
projectCommandExecutor, releaserProperties, versionsFetcher);
}
@Bean
@ConditionalOnMissingBean
DocumentationUpdater documentationUpdater(ProjectGitHandler projectGitHandler,
ReleaserProperties properties, TemplateGenerator templateGenerator,
@Autowired(
required = false) List<CustomProjectDocumentationUpdater> customProjectDocumentationUpdaters) {
return new DocumentationUpdater(projectGitHandler, properties, templateGenerator,
customProjectDocumentationUpdaters != null
? customProjectDocumentationUpdaters : new ArrayList<>());
}
@Bean
@ConditionalOnMissingBean
Releaser releaser(ProjectPomUpdater projectPomUpdater,
ProjectCommandExecutor projectCommandExecutor,
ProjectGitHandler projectGitHandler,
ProjectGitHubHandler projectGitHubHandler,
TemplateGenerator templateGenerator, GradleUpdater gradleUpdater,
SaganUpdater saganUpdater, DocumentationUpdater documentationUpdater,
PostReleaseActions postReleaseActions,
ReleaserProperties releaserProperties) {
return new Releaser(releaserProperties, projectPomUpdater, projectCommandExecutor,
projectGitHandler, projectGitHubHandler, templateGenerator, gradleUpdater,
saganUpdater, documentationUpdater, postReleaseActions);
}
@Bean
@ConditionalOnMissingBean
ReleaserPropertiesUpdater releaserPropertiesUpdater(ApplicationContext context) {
return new ReleaserPropertiesUpdater(context);
}
@Bean
@ConditionalOnMissingBean
Parser optionsParser(List<ReleaserTask> allTasks,
List<SingleProjectReleaserTask> singleProjectReleaserTasks,
ConfigurableApplicationContext context) {
return new OptionsParser(allTasks, singleProjectReleaserTasks, context);
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.ReleaserPropertiesAware;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.StringUtils;
/**
* @author Marcin Grzejszczak
*/
class ReleaserPropertiesUpdater {
private static final Logger log = LoggerFactory
.getLogger(ReleaserPropertiesUpdater.class);
private final ApplicationContext context;
ReleaserPropertiesUpdater(ApplicationContext context) {
this.context = context;
}
ReleaserProperties updateProperties(ReleaserProperties properties,
File clonedProjectFromOrg) {
ReleaserProperties props = updatePropertiesFromFile(properties,
clonedProjectFromOrg);
props.setWorkingDir(clonedProjectFromOrg.getAbsolutePath());
log.trace("Updated properties [\n\n{}\n\n]", props);
updateProperties(props);
return props;
}
void updateProperties(ReleaserProperties props) {
Map<String, ReleaserPropertiesAware> beans = this.context
.getBeansOfType(ReleaserPropertiesAware.class);
beans.values().forEach(aware -> aware.setReleaserProperties(props));
}
private ReleaserProperties updatePropertiesFromFile(ReleaserProperties copy,
File clonedProjectFromOrg) {
File releaserConfig = releaserConfig(clonedProjectFromOrg);
if (releaserConfig.exists()) {
try {
YamlPropertiesFactoryBean yamlProcessor = new YamlPropertiesFactoryBean();
yamlProcessor.setResources(new FileSystemResource(releaserConfig));
Properties properties = yamlProcessor.getObject();
ReleaserProperties releaserProperties = new Binder(
new MapConfigurationPropertySource(properties.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(),
e -> e.getValue().toString()))))
.bind("releaser",
ReleaserProperties.class)
.get();
log.info("config/releaser.yml found. Will update the current properties");
overrideProperties(releaserProperties, copy);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
else {
log.info(
"No config/releaser.yml found. Will NOT update the current properties");
}
log.info("Updating working directory to [{}]",
clonedProjectFromOrg.getAbsolutePath());
copy.setWorkingDir(clonedProjectFromOrg.getAbsolutePath());
return copy;
}
private void overrideProperties(ReleaserProperties fromProject,
ReleaserProperties copy) {
overrideCommandIfPresent(fromProject.getMaven(), copy.getMaven());
overrideCommandIfPresent(fromProject.getGradle(), copy.getGradle());
overrideMapIfPresent(() -> fromProject.getGradle().getGradlePropsSubstitution(),
s -> copy.getGradle().setGradlePropsSubstitution(s));
overrideListIfPresent(() -> fromProject.getGradle().getIgnoredGradleRegex(),
s -> copy.getGradle().setIgnoredGradleRegex(s));
overrideCommandIfPresent(fromProject.getBash(), copy.getBash());
}
private void overrideCommandIfPresent(ReleaserProperties.Command fromProject,
ReleaserProperties.Command commandCopy) {
overrideStringIfPresent(fromProject::getBuildCommand,
commandCopy::setBuildCommand);
overrideStringIfPresent(fromProject::getDeployCommand,
commandCopy::setDeployCommand);
overrideStringIfPresent(fromProject::getGenerateReleaseTrainDocsCommand,
commandCopy::setGenerateReleaseTrainDocsCommand);
overrideArrayIfPresent(fromProject::getPublishDocsCommands,
commandCopy::setPublishDocsCommands);
overrideStringIfPresent(fromProject::getDeployGuidesCommand,
commandCopy::setDeployGuidesCommand);
overrideStringIfPresent(fromProject::getSystemProperties,
commandCopy::setSystemProperties);
}
private void overrideStringIfPresent(Supplier<String> predicate,
Consumer<String> consumer) {
if (StringUtils.hasText(predicate.get())) {
consumer.accept(predicate.get());
}
}
private void overrideArrayIfPresent(Supplier<String[]> predicate,
Consumer<String[]> consumer) {
String[] strings = predicate.get();
if (strings.length > 0) {
consumer.accept(strings);
}
}
private void overrideMapIfPresent(Supplier<Map<String, String>> predicate,
Consumer<Map<String, String>> consumer) {
Map<String, String> strings = predicate.get();
if (!strings.isEmpty()) {
consumer.accept(strings);
}
}
private void overrideListIfPresent(Supplier<List<String>> predicate,
Consumer<List<String>> consumer) {
List<String> strings = predicate.get();
if (!strings.isEmpty()) {
consumer.accept(strings);
}
}
File releaserConfig(File clonedProjectFromOrg) {
return new File(clonedProjectFromOrg, "config/releaser.yml");
}
}

View File

@@ -0,0 +1,292 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.jakewharton.fliptables.FlipTableConverters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.util.StringUtils;
class SpringBatchExecutionResultHandler implements ExecutionResultHandler {
private static final Logger log = LoggerFactory
.getLogger(SpringBatchExecutionResultHandler.class);
private final JobExplorer jobExplorer;
private final ConfigurableApplicationContext context;
SpringBatchExecutionResultHandler(JobExplorer jobExplorer,
ConfigurableApplicationContext context) {
this.jobExplorer = jobExplorer;
this.context = context;
}
@Override
public void accept(ExecutionResult executionResult) {
buildSummaryTable();
if (executionResult.isFailure()) {
log.error("At least one failure occurred while running the release process",
executionResult.foundExceptions());
handleFailedBuild();
exitWithException();
return;
}
else if (executionResult.isUnstable()) {
log.warn(
"The release went fine, however at least one unstable post release task was found",
executionResult.foundExceptions());
handleUnstableException();
}
handleStableBuild();
exitSuccessfully();
}
void exitSuccessfully() {
SpringApplication.exit(this.context, () -> 0);
System.exit(0);
}
void exitWithException() {
SpringApplication.exit(this.context, () -> 1);
System.exit(1);
}
private void buildSummaryTable() {
List<String> jobNames = this.jobExplorer.getJobNames();
List<JobExecution> sortedJobExecutions = jobNames.stream()
.flatMap(name -> this.jobExplorer.findJobInstancesByJobName(name, 0, 100)
.stream())
.flatMap(instance -> this.jobExplorer.getJobExecutions(instance).stream())
.sorted(Comparator.comparing(JobExecution::getCreateTime))
.collect(Collectors.toList());
List<StepExecution> stepContexts = sortedJobExecutions.stream()
.flatMap(j -> j.getStepExecutions().stream())
.collect(Collectors.toCollection(LinkedList::new));
printTable(buildTable(stepContexts));
}
private List<Table> buildTable(List<StepExecution> stepContexts) {
return stepContexts.stream().map(step -> {
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
.format(step.getStartTime());
long millis = ChronoUnit.MILLIS.between(step.getStartTime().toInstant(),
step.getEndTime().toInstant());
ExecutionContext context = step.getExecutionContext();
ExecutionResultReport entity = (ExecutionResultReport) context.get("entity");
if (entity == null) {
return null;
}
String projectName = TrainPostReleaseReleaserTask.class
.isAssignableFrom(entity.getReleaserTaskType()) ? "postRelease"
: entity.getProjectName();
return new Table(date, time(millis), projectName, entity.getShortName(),
entity.getDescription(), entity.getState(), entity.getExceptions());
}).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedList::new));
}
private String time(long millis) {
long minutes = (millis / 1000) / 60;
long seconds = (millis / 1000) % 60;
if (minutes == 0 && seconds == 0) {
return millis + " ms";
}
else if (minutes == 0) {
return seconds + " s";
}
return minutes + " min " + seconds + " s";
}
private void printTable(List<Table> table) {
String string = "\n\n***** BUILD REPORT *****\n\n"
+ FlipTableConverters.fromIterable(table, Table.class)
+ "\n\n***** BUILD REPORT *****\n\n";
List<Table> brokenTasks = table.stream()
.filter(table1 -> StringUtils.hasText(table1.thrownException))
.collect(Collectors.toList());
if (!brokenTasks.isEmpty()) {
String brokenBuilds = "\n\n[BUILD UNSTABLE] The following release tasks are failing!\n\n"
+ brokenTasks.stream().map(table1 -> String.format(
"***** Project / Task : <%s/%s> ***** \nTask Description <%s>\nException Stacktrace \n\n%s",
table1.projectName, table1.taskCaption,
table1.taskDescription,
table1.exceptions + "\n"
+ table1.exceptions.stream()
.map(Throwable::getStackTrace)
.flatMap(e -> Arrays.stream(e))
.map(StackTraceElement::toString)
.collect(Collectors.joining("\n"))))
.collect(Collectors.joining("\n\n"));
log.warn(string + brokenBuilds);
}
else {
log.info(string);
}
}
// File creation required by Jenkins
private void handleUnstableException() {
File buildStatus = new File("build_status");
try {
buildStatus.createNewFile();
String text = "[BUILD UNSTABLE] The release happened successfully, but there were post release issues";
Files.write(buildStatus.toPath(), text.getBytes());
log.info(
"\n\n\n ___ _ _ ___ _ ___ _ _ _ _ ___ _____ _ ___ _ ___ \n"
+ " | _ ) | | |_ _| | | \\ | | | | \\| / __|_ _/_\\ | _ ) | | __|\n"
+ " | _ \\ |_| || || |__| |) | | |_| | .` \\__ \\ | |/ _ \\| _ \\ |__| _| \n"
+ " |___/\\___/|___|____|___/ \\___/|_|\\_|___/ |_/_/ \\_\\___/____|___|\n"
+ " ");
}
catch (IOException e) {
throw new IllegalStateException(
"[BUILD UNSTABLE] Couldn't create a file to show that the build is unstable");
}
}
// File creation required by Jenkins
private void handleStableBuild() {
File buildStatus = new File("build_status");
if (buildStatus.exists()) {
log.info("Build status file has already been created!");
return;
}
try {
buildStatus.createNewFile();
String text = "[BUILD STABLE] All the release steps have been successfully executed!";
Files.write(buildStatus.toPath(), text.getBytes());
log.info(
"\n\n\n ___ _ _ ___ _ ___ ___ _ _ ___ ___ ___ ___ ___ ___ _ _ _ \n"
+ " | _ ) | | |_ _| | | \\ / __| | | |/ __/ __| __/ __/ __| __| | | | | \n"
+ " | _ \\ |_| || || |__| |) | \\__ \\ |_| | (_| (__| _|\\__ \\__ \\ _|| |_| | |__ \n"
+ " |___/\\___/|___|____|___/ |___/\\___/ \\___\\___|___|___/___/_| \\___/|____|\n"
+ " ");
}
catch (IOException e) {
log.info("Failed to store the file but the build was stable");
}
}
// File creation required by Jenkins
private void handleFailedBuild() {
File buildStatus = new File("build_status");
if (buildStatus.exists()) {
log.info("Build status file has already been created!");
return;
}
try {
buildStatus.createNewFile();
String text = "[BUILD FAILED] There were exceptions while doing the release!";
Files.write(buildStatus.toPath(), text.getBytes());
log.info("\n\n\n ___ _ _ ___ _ ___ ___ _ ___ _ ___ ___ \n"
+ " | _ ) | | |_ _| | | \\ | __/_\\ |_ _| | | __| \\ \n"
+ " | _ \\ |_| || || |__| |) | | _/ _ \\ | || |__| _|| |) |\n"
+ " |___/\\___/|___|____|___/ |_/_/ \\_\\___|____|___|___/ \n"
+ " ");
}
catch (IOException e) {
throw new IllegalStateException(
"[BUILD FAILED] Couldn't create a file to show that the build is unstable");
}
}
}
class Table {
final String creationTime;
final String executionTime;
final String projectName;
final String taskCaption;
final String taskDescription;
final String taskState;
final String thrownException;
List<Throwable> exceptions;
Table(String creationTime, String executionTime, String projectName,
String taskCaption, String taskDescription, String taskState,
List<Throwable> exceptions) {
this.creationTime = creationTime;
this.executionTime = executionTime;
this.projectName = projectName;
this.taskCaption = taskCaption;
this.taskDescription = taskDescription;
this.taskState = taskState;
this.thrownException = exceptions == null ? "" : exceptions.stream()
// TODO: Last but most specific
.map(t -> NestedExceptionUtils.getMostSpecificCause(t).toString())
.collect(Collectors.joining("\n"));
this.exceptions = exceptions;
}
public String getExecutionTime() {
return this.executionTime;
}
public String getCreationTime() {
return this.creationTime;
}
public String getProjectName() {
return this.projectName;
}
public String getTaskCaption() {
return this.taskCaption;
}
public String getTaskDescription() {
return this.taskDescription;
}
public String getTaskState() {
return this.taskState;
}
public String getThrownException() {
return this.thrownException;
}
}

View File

@@ -0,0 +1,666 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import edu.emory.mathcs.backport.java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.tasks.CompositeReleaserTask;
import releaser.internal.tasks.PostReleaseReleaserTask;
import releaser.internal.tasks.ReleaseReleaserTask;
import releaser.internal.tasks.ReleaserTask;
import releaser.internal.tech.BuildUnstableException;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.FlowJobBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.builder.JobFlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.task.TaskExecutor;
class SpringBatchFlowRunner implements FlowRunner, Closeable {
private static final Logger log = LoggerFactory
.getLogger(SpringBatchFlowRunner.class);
private static final String MSG = "\nPress 'q' to quit, 's' to skip, any key to continue\n\n";
private final ConsoleInputStepSkipper stepSkipper;
private final StepBuilderFactory stepBuilderFactory;
private final JobBuilderFactory jobBuilderFactory;
private final ProjectsToRunFactory projectsToRunFactory;
private final JobLauncher jobLauncher;
private final FlowRunnerTaskExecutorSupplier flowRunnerTaskExecutorSupplier;
private static final List<TaskExecutor> EXECUTORS = new ArrayList<>();
private final ExecutorService executorService;
private final ReleaserProperties releaserProperties;
SpringBatchFlowRunner(StepBuilderFactory stepBuilderFactory,
JobBuilderFactory jobBuilderFactory,
ProjectsToRunFactory projectsToRunFactory, JobLauncher jobLauncher,
FlowRunnerTaskExecutorSupplier flowRunnerTaskExecutorSupplier,
ConfigurableApplicationContext context,
ReleaserProperties releaserProperties) {
this.stepBuilderFactory = stepBuilderFactory;
this.jobBuilderFactory = jobBuilderFactory;
this.projectsToRunFactory = projectsToRunFactory;
this.jobLauncher = jobLauncher;
this.flowRunnerTaskExecutorSupplier = flowRunnerTaskExecutorSupplier;
this.stepSkipper = new ConsoleInputStepSkipper(context);
this.releaserProperties = releaserProperties;
this.executorService = Executors.newFixedThreadPool(
this.releaserProperties.getMetaRelease().getReleaseGroupThreadCount());
}
@Override
public Decision beforeTask(Options options, ReleaserProperties properties,
ReleaserTask releaserTask) {
return decide(options, releaserTask);
}
private Step createStep(ReleaserTask releaserTask,
NamedArgumentsSupplier argsSupplier) {
return this.stepBuilderFactory
.get(argsSupplier.projectName + "_" + releaserTask.name())
.tasklet((contribution, chunkContext) -> {
Arguments args = argsSupplier.get();
FlowRunner.Decision decision = beforeTask(args.options,
args.properties, releaserTask);
if (decision == FlowRunner.Decision.CONTINUE) {
ExecutionResult result = runTask(releaserTask, args);
contribution.getStepExecution().getExecutionContext()
.put("result", result);
List<Throwable> errors = (List<Throwable>) contribution
.getStepExecution().getExecutionContext().get("errors");
RuntimeException exception = result.foundExceptions();
errors = addExceptionToErrors(errors, exception);
String status = result.isUnstable() ? "UNSTABLE"
: result.isFailure() ? "FAILURE" : "SUCCESS";
ExecutionResultReport entity = buildEntity(releaserTask, args,
status, errors);
contribution.getStepExecution().getExecutionContext()
.put("entity", entity);
if (result.isFailureOrUnstable()) {
contribution.getStepExecution().getExecutionContext()
.put("errors", errors);
}
}
else {
ExecutionResultReport entity = buildEntity(releaserTask, args,
"SKIPPED", Collections.emptyList());
contribution.getStepExecution().getExecutionContext()
.put("entity", entity);
log.info("Skipping step [{}]", releaserTask.name());
}
return RepeatStatus.FINISHED;
}).listener(releaserListener(argsSupplier, releaserTask)).build();
}
private List<Throwable> addExceptionToErrors(List<Throwable> errors,
RuntimeException exception) {
if (errors == null) {
errors = new LinkedList<>();
}
if (exception != null) {
errors.add(exception);
}
return errors;
}
private ExecutionResultReport buildEntity(ReleaserTask releaserTask, Arguments args,
String state, List<Throwable> errors) {
String projectName = args.project.getName();
String shortName = releaserTask.getClass().getSimpleName();
String description = releaserTask.description();
Class<? extends ReleaserTask> releaseType = releaserTask.getClass();
return new ExecutionResultReport(projectName, shortName, description, releaseType,
state, errors);
}
private ExecutionResult runTask(ReleaserTask releaserTask, Arguments args) {
ExecutionResult executionResult = releaserTask.apply(args);
if (executionResult.isFailure() && releaserTask instanceof ReleaseReleaserTask) {
throw executionResult.foundExceptions();
}
return executionResult;
}
private StepExecutionListener releaserListener(NamedArgumentsSupplier argsSupplier,
ReleaserTask releaserTask) {
return new StepExecutionListenerSupport() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
Arguments args = argsSupplier.get();
FlowRunner.Decision decision = afterTask(args.options, args.properties,
releaserTask);
if (decision == FlowRunner.Decision.ABORT) {
return ExitStatus.FAILED;
}
ExecutionResult result = (ExecutionResult) stepExecution
.getExecutionContext().get("result");
if (result == null || result.isSuccess()) {
return stepExecution.getExitStatus();
}
else if (result.isUnstable()) {
return new ExitStatus(BuildUnstableException.EXIT_CODE,
BuildUnstableException.DESCRIPTION);
}
else if (result.isFailure()) {
return ExitStatus.FAILED;
}
return ExitStatus.COMPLETED;
}
};
}
@Override
public ExecutionResult runReleaseTasks(Options options, ReleaserProperties properties,
ProjectsToRun projectsToRun, TasksToRun tasksToRun) {
ProjectsToReleaseGroups groups = new ProjectsToReleaseGroups(properties);
List<ReleaseGroup> releaseGroups = groups.toReleaseGroup(projectsToRun);
if (groups.hasGroups()) {
log.info("Found the following release groups {}", releaseGroups);
}
List<StuffToRun> flows = releaseGroups.stream()
.map(group -> buildFlowForGroup(tasksToRun, group))
.collect(Collectors.toCollection(LinkedList::new));
Iterator<StuffToRun> flowsIterator = flows.iterator();
if (!flowsIterator.hasNext()) {
// nothing to run
return ExecutionResult.success();
}
if (flows.stream().allMatch(StuffToRun::hasCompositeTask)) {
log.info("You've picked composite jobs to run");
return runComposites(flows, groups);
}
return runJob(buildJobForFlows(flowsIterator));
}
private ExecutionResult runComposites(List<StuffToRun> flows,
ProjectsToReleaseGroups groups) {
if (groups.hasGroups()) {
// will run in parallel
List<ExecutionResult> results = new LinkedList<>();
for (StuffToRun flow : flows) {
log.info("Releasing group [{}]", flow.releaseGroup);
ExecutionResult executionResult = runInParallel(flow, flow.task).stream()
.map(this::result)
.reduce(new ExecutionResult(), ExecutionResult::merge);
log.info("Group [{}] execution result is [{}]", flow.releaseGroup,
executionResult);
if (executionResult.isFailure()) {
// stop running any additional flows when an release task exception
// was found
throw executionResult.foundExceptions();
}
results.add(executionResult);
}
return results.stream().reduce(new ExecutionResult(), ExecutionResult::merge);
}
// will run in sequence
return flows.stream().map(s -> runInSequence(s, s.task)).flatMap(s -> s)
.reduce(new ExecutionResult(), ExecutionResult::merge);
}
private ExecutionResult result(Future<ExecutionResult> future) {
try {
return future.get();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
private List<Future<ExecutionResult>> runInParallel(StuffToRun stuffToRun,
CompositeReleaserTask releaserTask) {
log.info("Running composite tasks in parallel for {}", stuffToRun.releaseGroup);
return stuffToRun.releaseGroup.projectsToRun.stream().map(s -> {
log.info("Scheduling a build for project [{}]", s.projectName());
return this.executorService.submit(() -> {
log.info("Running a composite task [{}] in parallel",
releaserTask.name());
return releaserTask.apply(Arguments.forProject(s.get()));
});
}).collect(Collectors.toCollection(LinkedList::new));
}
private Stream<ExecutionResult> runInSequence(StuffToRun stuffToRun,
CompositeReleaserTask releaserTask) {
return stuffToRun.releaseGroup.projectsToRun.stream()
.map(s -> releaserTask.apply(Arguments.forProject(s.get())));
}
private Job buildJobForFlows(Iterator<StuffToRun> flowsIterator) {
JobBuilder release = this.jobBuilderFactory
.get("release_" + System.currentTimeMillis());
StuffToRun stuffToRun = flowsIterator.next();
Flow first = stuffToRun.flow;
JobFlowBuilder start = release.start(first);
FlowBuilder<FlowJobBuilder> next = null;
while (flowsIterator.hasNext()) {
StuffToRun toRun = flowsIterator.next();
next = start.next(toRun.flow);
}
FlowJobBuilder builder = next != null ? next.build() : start.build();
return builder.build();
}
private StuffToRun buildFlowForGroup(TasksToRun tasksToRun, ReleaseGroup group) {
FlowBuilder<Flow> flowBuilder = new FlowBuilder<>(
group.flowName() + (group.shouldRunInParallel() ? "_Parallel" : "") + "_"
+ System.currentTimeMillis());
Iterator<ProjectToRun.ProjectToRunSupplier> iterator = group.iterator();
if (!iterator.hasNext() || tasksToRun.isEmpty()) {
return new StuffToRun(group, flowBuilder.build());
}
// only one project to run - run in sequence
final ProjectToRun.ProjectToRunSupplier project = iterator.next();
if (tasksToRun.size() == 1
&& tasksToRun.get(0) instanceof CompositeReleaserTask) {
return new StuffToRun(group, (CompositeReleaserTask) tasksToRun.get(0));
}
flowBuilder.start(toFlowOfTasks(tasksToRun,
new NamedArgumentsSupplier(project.projectName(),
() -> Arguments.forProject(project.get())),
flowBuilder(project.projectName())));
if (!iterator.hasNext()) {
return new StuffToRun(group, flowBuilder.build());
}
// more projects, run them in parallel
FlowBuilder.SplitBuilder<Flow> split = flowBuilder.split(taskExecutor());
List<Flow> flows = new LinkedList<>();
while (iterator.hasNext()) {
final ProjectToRun.ProjectToRunSupplier nextProject = iterator.next();
String projectName = project.projectName();
flows.add(toFlowOfTasks(tasksToRun,
new NamedArgumentsSupplier(project.projectName(),
() -> Arguments.forProject(nextProject.get())),
flowBuilder(projectName)));
}
return new StuffToRun(group, split.add(flows.toArray(new Flow[0])).end());
}
private FlowBuilder<Flow> flowBuilder(String projectName) {
return new FlowBuilder<>(projectName + "_Flow_" + System.currentTimeMillis());
}
@Override
public ExecutionResult runPostReleaseTrainTasks(Options options,
ReleaserProperties properties, String taskName, TasksToRun tasksToRun) {
ProjectsToRun projectsToRun = postReleaseTrainProjects(
new OptionsAndProperties(properties, options));
Flow flow = postReleaseFlow(tasksToRun, properties, projectsToRun);
String name = taskName + "_" + System.currentTimeMillis();
if (flow == null) {
log.info("No release train post release tasks to run, will do nothing");
return ExecutionResult.success();
}
Job job = this.jobBuilderFactory.get(name).start(flow).build().build();
return runJob(job);
}
private ProjectsToRun postReleaseTrainProjects(OptionsAndProperties options) {
return this.projectsToRunFactory.postReleaseTrain(options);
}
private Flow toFlowOfTasks(TasksToRun tasksToRun, NamedArgumentsSupplier args,
FlowBuilder<Flow> flowBuilder) {
Iterator<? extends ReleaserTask> iterator = tasksToRun.iterator();
ReleaserTask task = iterator.next();
flowBuilder.start(createStep(task, args));
while (iterator.hasNext()) {
flowBuilder.next(createStep(iterator.next(), args));
}
return flowBuilder.build();
}
private ExecutionResult runJob(Job job) {
try {
JobExecution execution = this.jobLauncher.run(job, new JobParameters());
if (!ExitStatus.COMPLETED.equals(execution.getExitStatus())) {
return ExecutionResult.failure(new IllegalStateException(
"Job failed to get executed successfully. Failed with exit code ["
+ execution.getExitStatus().getExitCode()
+ "] and description ["
+ execution.getExitStatus().getExitDescription() + "]"));
}
List<Exception> thrownExceptions = exceptionsThrownBySteps(execution);
if (thrownExceptions.isEmpty()) {
return ExecutionResult.success();
}
return new ExecutionResult(thrownExceptions);
}
catch (JobExecutionException ex) {
return ExecutionResult.failure(ex);
}
}
@SuppressWarnings("unchecked")
private List<Exception> exceptionsThrownBySteps(JobExecution execution) {
return execution.getStepExecutions().stream()
.map(e -> e.getExecutionContext().get("errors") != null
? (List) e.getExecutionContext().get("errors") : new LinkedList())
.reduce(new LinkedList<Exception>(), (o, o2) -> {
o.addAll(o2);
return o;
});
}
private Flow postReleaseFlow(TasksToRun tasksToRun, ReleaserProperties properties,
ProjectsToRun projectsToRun) {
Iterator<? extends ReleaserTask> iterator = tasksToRun.iterator();
if (!iterator.hasNext()) {
return null;
}
ReleaserTask task = iterator.next();
Flow flow = flow(properties, projectsToRun, task);
FlowBuilder<Flow> flowBuilder = new FlowBuilder<Flow>(
"parallelPostRelease_" + System.currentTimeMillis()).start(flow);
if (!iterator.hasNext()) {
return flowBuilder.build();
}
FlowBuilder.SplitBuilder<Flow> builder = flowBuilder.split(taskExecutor());
List<Flow> flows = new LinkedList<>();
while (iterator.hasNext()) {
flows.add(flow(properties, projectsToRun, iterator.next()));
}
Flow[] objects = flows.toArray(new Flow[0]);
return builder.add(objects).build();
}
private TaskExecutor taskExecutor() {
TaskExecutor taskExecutor = this.flowRunnerTaskExecutorSupplier.get();
EXECUTORS.add(taskExecutor);
return taskExecutor;
}
private Flow flow(ReleaserProperties properties, ProjectsToRun projectsToRun,
ReleaserTask task) {
return new FlowBuilder<Flow>(task.name() + "Flow")
.start(createStep(task, new NamedArgumentsSupplier("postRelease",
() -> Arguments.forPostRelease(properties, projectsToRun))))
.build();
}
Decision decide(Options options, ReleaserTask task) {
boolean interactive = options.interactive;
printLog(interactive, task);
if (interactive) {
boolean skipStep = this.stepSkipper.skipStep();
return skipStep ? Decision.SKIP : Decision.CONTINUE;
}
return Decision.CONTINUE;
}
private void printLog(boolean interactive, ReleaserTask task) {
String taskType = task instanceof PostReleaseReleaserTask ? "Post Release Task"
: "Release Task";
log.info("\n\n\n=== {} [{}] ===\n\n{} {}\n\n", task.header(), taskType,
task.description(), interactive ? MSG : "");
}
@Override
public void close() {
EXECUTORS.stream().filter(t -> t instanceof DisposableBean).forEach(e -> {
try {
((DisposableBean) e).destroy();
}
catch (Exception ex) {
log.debug("Exception occurred while trying to destroy the bean", ex);
}
});
this.executorService.shutdown();
}
}
class NamedArgumentsSupplier implements Supplier<Arguments> {
final String projectName;
final Supplier<Arguments> argumentsSupplier;
final AtomicReference<Arguments> arguments = new AtomicReference<>();
NamedArgumentsSupplier(String projectName, Supplier<Arguments> argumentsSupplier) {
this.projectName = projectName;
this.argumentsSupplier = argumentsSupplier;
}
@Override
public Arguments get() {
if (this.arguments.get() != null) {
return arguments.get();
}
Arguments arguments = this.argumentsSupplier.get();
this.arguments.set(arguments);
return arguments;
}
}
class ConsoleInputStepSkipper {
private final ConfigurableApplicationContext context;
ConsoleInputStepSkipper(ConfigurableApplicationContext context) {
this.context = context;
}
public boolean skipStep() {
String input = chosenOption();
switch (input.toLowerCase()) {
case "s":
return true;
case "q":
SpringApplication.exit(this.context, () -> 0);
System.exit(0);
return true;
default:
return false;
}
}
String chosenOption() {
return System.console().readLine();
}
}
class StuffToRun {
final ReleaseGroup releaseGroup;
final Flow flow;
final CompositeReleaserTask task;
StuffToRun(ReleaseGroup releaseGroup, Flow flow) {
this.releaseGroup = releaseGroup;
this.flow = flow;
this.task = null;
}
StuffToRun(ReleaseGroup releaseGroup, CompositeReleaserTask task) {
this.releaseGroup = releaseGroup;
this.task = task;
this.flow = null;
}
boolean hasCompositeTask() {
return this.task != null;
}
}
class ProjectsToReleaseGroups {
private final ReleaserProperties releaserProperties;
ProjectsToReleaseGroups(ReleaserProperties releaserProperties) {
this.releaserProperties = releaserProperties;
}
List<ReleaseGroup> toReleaseGroup(ProjectsToRun projectsToRun) {
ReleaseGroups releaseGroups = new ReleaseGroups(
this.releaserProperties.getMetaRelease().getReleaseGroups());
return releaseGroup(releaseGroups, null, new LinkedList<>(),
projectsToRun.iterator());
}
boolean hasGroups() {
return !this.releaserProperties.getMetaRelease().getReleaseGroups().isEmpty();
}
private List<ReleaseGroup> releaseGroup(ReleaseGroups releaseGroups,
ReleaseGroup currentGroup, List<ReleaseGroup> merged,
Iterator<ProjectToRun.ProjectToRunSupplier> iterator) {
if (iterator.hasNext()) {
// sleuth
ProjectToRun.ProjectToRunSupplier supplier = iterator.next();
// sleuth,contract,gateway
String[] group = releaseGroups.group(supplier.projectName());
if (currentGroup != null && !currentGroup.sameGroup(group)) {
// we've been adding things to the current group, but we need to stop now
merged.add(currentGroup);
currentGroup = null;
}
if (group.length == 0) {
// if no group matching, return a single entry
ReleaseGroup current = new ReleaseGroup(supplier, group);
merged.add(current);
return releaseGroup(releaseGroups, null, merged, iterator);
}
// if still in group add to the current one
if (currentGroup != null) {
currentGroup.add(supplier);
}
else {
currentGroup = new ReleaseGroup(supplier, group);
}
return releaseGroup(releaseGroups, currentGroup, merged, iterator);
}
if (currentGroup != null) {
merged.add(currentGroup);
}
return merged;
}
}
class ReleaseGroup {
final List<ProjectToRun.ProjectToRunSupplier> projectsToRun = new LinkedList<>();
final String[] group;
ReleaseGroup(ProjectToRun.ProjectToRunSupplier supplier, String[] group) {
this.projectsToRun.add(supplier);
this.group = group;
}
Iterator<ProjectToRun.ProjectToRunSupplier> iterator() {
return projectsToRun.iterator();
}
void add(ProjectToRun.ProjectToRunSupplier projectToRun) {
this.projectsToRun.add(projectToRun);
}
boolean sameGroup(String[] group) {
return Arrays.equals(group, this.group);
}
boolean shouldRunInParallel() {
return this.projectsToRun.size() > 1;
}
String flowName() {
return this.projectsToRun.get(0).projectName() + "_Flow";
}
private String projectName() {
return this.projectsToRun.isEmpty() ? "EMPTY"
: this.projectsToRun.get(0).projectName();
}
@Override
public String toString() {
return "ReleaseGroup{" + "group="
+ (group.length > 0 ? Arrays.toString(group) : projectName()) + '}';
}
}
class ReleaseGroups {
private final List<String> releaseGroups;
ReleaseGroups(List<String> releaseGroups) {
this.releaseGroups = releaseGroups;
}
String[] group(String name) {
String foundGroup = this.releaseGroups.stream().filter(s -> s.contains(name))
.findFirst().orElse(null);
if (foundGroup == null) {
return new String[0];
}
return foundGroup.split(",");
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import releaser.internal.options.Options;
/**
* Contract for releasing a project or train .
*/
public interface SpringReleaser {
/**
* Perform a release with default options.
* @return result of the release
*/
ExecutionResult release();
/**
* Perform a release with provided options.
* @param options options for the release
* @return result of the release
*/
ExecutionResult release(Options options);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.Collection;
import java.util.LinkedList;
import releaser.internal.tasks.ReleaserTask;
public class TasksToRun extends LinkedList<ReleaserTask> {
public TasksToRun() {
}
public TasksToRun(Collection<? extends ReleaserTask> c) {
super(c);
}
public TasksToRun(ReleaserTask c) {
add(c);
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.tasks.ReleaserTask;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
import releaser.internal.tasks.composite.DryRunCompositeTask;
import releaser.internal.tasks.composite.MetaReleaseCompositeTask;
import releaser.internal.tasks.composite.MetaReleaseDryRunCompositeTask;
import releaser.internal.tasks.composite.ReleaseCompositeTask;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.StringUtils;
class TasksToRunFactory {
private static final Logger log = LoggerFactory.getLogger(TasksToRunFactory.class);
private final ApplicationContext context;
TasksToRunFactory(ApplicationContext context) {
this.context = context;
}
TasksToRun release(OptionsAndProperties optionsAndProperties) {
TasksToRun tasks = releaseTasks(optionsAndProperties);
tasks.forEach(t -> t.setup(optionsAndProperties.options,
optionsAndProperties.properties));
log.info("Will run the following tasks {}",
tasks.stream().map(t -> t.getClass().getSimpleName()).collect(Collectors
.toCollection((Supplier<LinkedList<String>>) LinkedList::new)));
return tasks;
}
private TasksToRun releaseTasks(OptionsAndProperties optionsAndProperties) {
Options options = optionsAndProperties.options;
ReleaserProperties properties = optionsAndProperties.properties;
if (properties.isPostReleaseTasksOnly()) {
return new TasksToRun();
}
if (options.metaRelease) {
if (options.dryRun) {
return new TasksToRun(
this.context.getBean(MetaReleaseDryRunCompositeTask.class));
}
return new TasksToRun(this.context.getBean(MetaReleaseCompositeTask.class));
}
if (options.dryRun) {
return new TasksToRun(this.context.getBean(DryRunCompositeTask.class));
}
else if (options.fullRelease) {
return new TasksToRun(this.context.getBean(ReleaseCompositeTask.class));
}
// A single project release
List<ReleaserTask> tasks = new LinkedList<>(
this.context.getBeansOfType(ReleaserTask.class).values());
tasks.sort(AnnotationAwareOrderComparator.INSTANCE);
return tasksToRunForSingleProject(options, tasks);
}
TasksToRun postRelease(Options options) {
if (!options.metaRelease || options.dryRun) {
return new TasksToRun();
}
List<ReleaserTask> tasks = new LinkedList<>(
this.context.getBeansOfType(TrainPostReleaseReleaserTask.class).values());
tasks.sort(AnnotationAwareOrderComparator.INSTANCE);
return new TasksToRun(tasks);
}
private TasksToRun tasksToRunForSingleProject(Options options,
List<ReleaserTask> tasks) {
if (StringUtils.hasText(options.startFrom)) {
return startFrom(tasks, options);
}
else if (StringUtils.hasText(options.range)) {
return range(tasks, options.range);
}
else if (!options.taskNames.isEmpty()) {
return tasks(tasks, options.taskNames);
}
else if (options.interactive) {
return interactiveOnly(tasks);
}
else {
throw new IllegalStateException("You haven't picked any recognizable option");
}
}
private TasksToRun interactiveOnly(List<ReleaserTask> tasks) {
log.info(buildOptionsText(tasks).toString());
return taskFromOption(tasks);
}
private TasksToRun tasks(List<ReleaserTask> tasks, List<String> taskNames) {
return tasks.stream().filter(t -> taskNames.contains(t.name()))
.collect(Collectors.toCollection(TasksToRun::new));
}
private TasksToRun range(List<ReleaserTask> tasks, String range) {
String[] splitRange = range.split("-");
String start = splitRange[0];
String stop = "";
if (splitRange.length == 2) {
stop = splitRange[1];
}
boolean sameRange = start.equals(stop);
TasksToRun tasksToRun = new TasksToRun();
for (ReleaserTask task : tasks) {
if (start.equals(task.name()) || start.equals(task.shortName())) {
tasksToRun.add(task);
if (sameRange) {
break;
}
}
else if (stop.equals(task.name()) || stop.equals(task.shortName())) {
tasksToRun.add(task);
break;
}
}
return tasksToRun;
}
private TasksToRun startFrom(List<ReleaserTask> tasks, Options options) {
TasksToRun tasksToRun = new TasksToRun();
for (ReleaserTask task : tasks) {
if (options.startFrom.trim().equals(task.name())
|| options.startFrom.trim().equals(task.shortName())) {
tasksToRun.add(task);
}
}
return tasksToRun;
}
private StringBuilder buildOptionsText(List<ReleaserTask> allTasks) {
StringBuilder msg = new StringBuilder();
msg.append("\n\n\n=== WHAT DO YOU WANT TO DO? ===\n\n");
for (int i = 0; i < allTasks.size(); i++) {
msg.append(i).append(") ").append(allTasks.get(i).description()).append("\n");
}
msg.append("\n").append(
"You can pick a range of options by using the hyphen - e.g. '2-4' will execute jobs [2,3,4]\n");
msg.append("You can execute all tasks starting from a job "
+ "by using a hyphen and providing only one "
+ "number - e.g. '8-' will execute jobs [8,9,10]\n");
msg.append("You can execute given tasks by providing a "
+ "comma separated list of tasks - e.g. "
+ "'3,7,8' will execute jobs [3,7,8]\n");
msg.append("\n").append("You can press 'q' to quit\n\n");
return msg;
}
TasksToRun taskFromOption(List<ReleaserTask> tasks) {
String input = chosenOption();
if ("q".equals(input.toLowerCase())) {
SpringApplication.exit(this.context, () -> 0);
System.exit(0);
return null;
}
if (input.contains("-")) {
return rangeInteractive(tasks, input);
}
else if (input.contains(",")) {
return tasksInteractive(tasks, input);
}
else {
return singleTask(tasks, input);
}
}
private TasksToRun singleTask(List<ReleaserTask> tasks, String input) {
int chosenOption = Integer.parseInt(input);
ReleaserTask task = tasks.get(chosenOption);
log.info("\n\n\nYou chose [{}]: [{}]\n\n\n", chosenOption, task.description());
return new TasksToRun(task);
}
private TasksToRun tasksInteractive(List<ReleaserTask> tasks, String input) {
List<String> tasksFromInput = Arrays.asList(input.split(","));
List<String> taskNames = new ArrayList<>();
for (String task : tasksFromInput) {
int taskIndex = Integer.parseInt(task);
taskNames.add(tasks.get(taskIndex).name());
}
return tasks(tasks, taskNames);
}
private TasksToRun rangeInteractive(List<ReleaserTask> tasks, String input) {
String[] range = input.split("-");
Integer start = Integer.valueOf(range[0]);
Integer stop = null;
if (range.length == 2) {
stop = Integer.valueOf(range[1]);
}
String firstName = tasks.get(start).name();
String second = stop != null ? tasks.get(stop).name() : "";
return range(tasks, firstName + "-" + second);
}
String chosenOption() {
return System.console() == null ? "-1" : System.console().readLine();
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.Releaser;
import releaser.internal.ReleaserProperties;
import releaser.internal.project.ProjectVersion;
import releaser.internal.project.Projects;
import org.springframework.util.StringUtils;
class VersionsToBumpFactory implements Closeable {
private static final Logger log = LoggerFactory
.getLogger(VersionsToBumpFactory.class);
private static final Map<File, ProjectsFromBom> CACHE = new ConcurrentHashMap<>();
private final Releaser releaser;
private final ReleaserProperties properties;
VersionsToBumpFactory(Releaser releaser, ReleaserProperties properties) {
this.releaser = releaser;
this.properties = properties;
}
ProjectsFromBom withProject(File project) {
return CACHE.computeIfAbsent(project, file -> {
log.info("Fetch from git [{}], meta release [{}], project [{}]",
this.properties.getGit().isFetchVersionsFromGit(),
this.properties.getMetaRelease().isEnabled(), project);
if (this.properties.getGit().isFetchVersionsFromGit()
&& !this.properties.getMetaRelease().isEnabled()) {
return fetchVersionsFromGitForSingleProject(project);
}
return fetchVersionsFromFixedProjects(project);
});
}
private ProjectsFromBom fetchVersionsFromFixedProjects(File project) {
ProjectVersion originalVersion = new ProjectVersion(project);
Projects fixedVersions = this.releaser.fixedVersions();
String fixedVersionForProject = fixedVersions.forName(project.getName()).version;
ProjectVersion versionFromBom = StringUtils.hasText(fixedVersionForProject)
? new ProjectVersion(originalVersion.projectName, fixedVersionForProject)
: new ProjectVersion(project);
fixedVersions.add(versionFromBom);
printSettingVersionFromFixedVersions(fixedVersions);
return cachedProjectsFromBom(project, versionFromBom, fixedVersions);
}
private ProjectsFromBom fetchVersionsFromGitForSingleProject(File project) {
printVersionRetrieval();
Projects projectsToUpdate = this.releaser.retrieveVersionsFromBom();
ProjectVersion versionFromBom = assertNoSnapshotsForANonSnapshotProject(project,
projectsToUpdate);
return cachedProjectsFromBom(project, versionFromBom, projectsToUpdate);
}
private ProjectsFromBom cachedProjectsFromBom(File project,
ProjectVersion versionFromBom, Projects projectsToUpdate) {
return new ProjectsFromBom(projectsToUpdate, versionFromBom);
}
ProjectVersion assertNoSnapshotsForANonSnapshotProject(File project,
Projects projectsToUpdate) {
ProjectVersion versionFromBom;
versionFromBom = projectsToUpdate.forFile(project);
assertNoSnapshotsForANonSnapshotProject(projectsToUpdate, versionFromBom);
return versionFromBom;
}
private void assertNoSnapshotsForANonSnapshotProject(Projects projects,
ProjectVersion versionFromBom) {
if (!versionFromBom.isSnapshot() && projects.containsSnapshots()) {
throw new IllegalStateException("You are trying to release a non snapshot "
+ "version [" + versionFromBom + "] of the project ["
+ versionFromBom.projectName + "] but "
+ "there is at least one SNAPSHOT library version in the Spring Cloud Release project");
}
}
private void printVersionRetrieval() {
log.info("\n\n\n=== RETRIEVING VERSIONS ===\n\nWill clone the BOM project"
+ " to retrieve all versions");
}
private void printSettingVersionFromFixedVersions(Projects projectsToUpdate) {
log.info(
"\n\n\n=== RETRIEVED VERSIONS ===\n\nWill use the fixed versions"
+ " of projects\n\n{}",
projectsToUpdate.stream().map(p -> p.projectName + " => " + p.version)
.collect(Collectors.joining("\n")));
}
@Override
public void close() throws IOException {
CACHE.clear();
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for a composite task. Composite tasks are capable of executing other
* tasks - e.g. "fullRelease" composite task executes all single project releaser tasks.
*/
public interface CompositeReleaserTask extends ReleaserTask {
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Allows to disable the default flow and gives an option to manually setup the whole
* releaser flow.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@ConditionalOnProperty(value = "releaser.flow.default-enabled", matchIfMissing = true)
public @interface ConditionalOnDefaultFlowEnabled {
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for running release tasks in a dry run mode. You should make a task
* implement this interface when you want it to be a part of the dry-run option. For
* example a typical scenario of a dry-run is to just update the poms / build.gradle
* files, build and install the artifacts locally.
*/
public interface DryRunReleaseReleaserTask extends ReleaseReleaserTask {
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
/**
* Marker interface for running post release tasks.
*/
public interface PostReleaseReleaserTask extends ReleaserTask {
/**
* Executes the task but catches exceptions and converts them into result. When an
* exception is throw will treat it as an instability.
* @param args - arguments to run the task
* @return execution result
*/
default ExecutionResult apply(Arguments args) {
try {
return runTask(args);
}
catch (Exception ex) {
return ExecutionResult.unstable(ex);
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for a post release task for a single project. Example: update the
* GitHub milestone for the project.
*/
public interface ProjectPostReleaseReleaserTask
extends SingleProjectReleaserTask, PostReleaseReleaserTask {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for a single project release task. Unlike the post release task, if
* this one fails, it should fail the release process. Example: build the project.
*/
public interface ReleaseReleaserTask extends SingleProjectReleaserTask {
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
import java.util.function.Function;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tech.BuildUnstableException;
import org.springframework.core.Ordered;
/**
* Describes a single release task.
*/
public interface ReleaserTask extends Ordered, Function<Arguments, ExecutionResult> {
/**
* @return name of the release task (e.g. 'Build')
*/
String name();
/**
* @return short name of the release task (e.g. 'b')
*/
String shortName();
/**
* @return header presented in the logs when running the task
*/
String header();
/**
* @return meaningful description of the release task.
*/
String description();
/**
* If necessary mutates options and properties for the release task. This can be
* useful for composite tasks that impose certain configuration.
* @param options - options to mutate
* @param properties - properties to mutate
*/
default void setup(Options options, ReleaserProperties properties) {
}
/**
* Executes the task but catches exceptions and converts them into result. Knows how
* to differentiate between a failure and instability.
* @param args - arguments to run the task
* @return execution result
*/
default ExecutionResult apply(Arguments args) {
try {
return runTask(args);
}
catch (BuildUnstableException ex) {
return ExecutionResult.unstable(ex);
}
catch (Exception ex) {
return ExecutionResult.failure(ex);
}
}
/**
* Main task execution logic.
* @param args - arguments for the job
* @return execution result of the job
* @throws BuildUnstableException - when the task failed but the release flow
* shouldn't be stopped (which means that the build is unstable)
* @throws RuntimeException - when the task failed for any other reason
*/
ExecutionResult runTask(Arguments args)
throws BuildUnstableException, RuntimeException;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for a task for a single project.
*/
public interface SingleProjectReleaserTask extends ReleaserTask {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks;
/**
* Marker interface for a post release task for the whole release train. Example: update
* the documentation of the release train.
*/
public interface TrainPostReleaseReleaserTask extends PostReleaseReleaserTask {
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import releaser.internal.tasks.ConditionalOnDefaultFlowEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnDefaultFlowEnabled
class CompositeTasksConfiguration {
@Bean
@ConditionalOnMissingBean
DryRunCompositeTask dryRunCompositeTask(ApplicationContext context) {
return new DryRunCompositeTask(context);
}
@Bean
@ConditionalOnMissingBean
MetaReleaseCompositeTask metaReleaseCompositeTask(ApplicationContext context) {
return new MetaReleaseCompositeTask(context);
}
@Bean
@ConditionalOnMissingBean
MetaReleaseDryRunCompositeTask metaReleaseDryRunCompositeTask(
ApplicationContext context) {
return new MetaReleaseDryRunCompositeTask(context);
}
@Bean
@ConditionalOnMissingBean
ReleaseCompositeTask releaseCompositeTask(ApplicationContext context) {
return new ReleaseCompositeTask(context);
}
@Bean
@ConditionalOnMissingBean
ReleaseVerboseCompositeTask releaseVerboseCompositeTask(ApplicationContext context) {
return new ReleaseVerboseCompositeTask(context);
}
@Bean
@ConditionalOnMissingBean
TrainPostReleaseCompositeTask trainPostReleaseCompositeTask(
ApplicationContext context) {
return new TrainPostReleaseCompositeTask(context);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.spring.FlowRunner;
import releaser.internal.spring.ProjectToRun;
import releaser.internal.spring.ProjectsToRun;
import releaser.internal.spring.TasksToRun;
import releaser.internal.tasks.CompositeReleaserTask;
import releaser.internal.tasks.DryRunReleaseReleaserTask;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Marked by {@link Options#dryRun}.
*/
public class DryRunCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -60;
private static final Logger log = LoggerFactory.getLogger(DryRunCompositeTask.class);
private final ApplicationContext context;
private FlowRunner flowRunner;
public DryRunCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "dryRun";
}
@Override
public String shortName() {
return "dr";
}
@Override
public String header() {
return "DRY RUN";
}
@Override
public String description() {
return "Perform a dry run release of a single project - bumps versions and installs them locally";
}
@Override
public ExecutionResult runTask(Arguments args) {
Map<String, DryRunReleaseReleaserTask> dryRunTasks = this.context
.getBeansOfType(DryRunReleaseReleaserTask.class);
List<DryRunReleaseReleaserTask> values = new LinkedList<>(dryRunTasks.values());
values.sort(AnnotationAwareOrderComparator.INSTANCE);
log.info("For project [{}], found the following dry run tasks {}",
args.project.getName(),
values.stream().map(r -> r.getClass().getSimpleName())
.collect(Collectors.toCollection(LinkedList::new)));
return flowRunner().runReleaseTasks(args.options, args.properties,
new ProjectsToRun(new ProjectToRun.ProjectToRunSupplier(
args.originalVersion.projectName, () -> args.projectToRun)),
new TasksToRun(values));
}
@Override
public void setup(Options options, ReleaserProperties properties) {
options.dryRun = true;
}
@Override
public int getOrder() {
return DryRunCompositeTask.ORDER;
}
private FlowRunner flowRunner() {
if (this.flowRunner == null) {
this.flowRunner = this.context.getBean(FlowRunner.class);
}
return this.flowRunner;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.CompositeReleaserTask;
import org.springframework.context.ApplicationContext;
/**
* Marked by {@link Options#metaRelease}.
*/
public class MetaReleaseCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -80;
private static final Logger log = LoggerFactory
.getLogger(MetaReleaseCompositeTask.class);
private final ApplicationContext context;
private ReleaseCompositeTask releaserCompositeTask;
public MetaReleaseCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "metaRelease";
}
@Override
public String shortName() {
return "x";
}
@Override
public String header() {
return "META RELEASE";
}
@Override
public String description() {
return "Perform a meta release of projects";
}
@Override
public ExecutionResult runTask(Arguments args) {
return releaserCompositeTask().apply(args);
}
@Override
public void setup(Options options, ReleaserProperties properties) {
properties.getGit().setFetchVersionsFromGit(false);
properties.getMetaRelease().setEnabled(true);
options.fullRelease = true;
}
@Override
public int getOrder() {
return MetaReleaseCompositeTask.ORDER;
}
private ReleaseCompositeTask releaserCompositeTask() {
if (this.releaserCompositeTask == null) {
this.releaserCompositeTask = this.context.getBean(ReleaseCompositeTask.class);
}
return this.releaserCompositeTask;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.CompositeReleaserTask;
import org.springframework.context.ApplicationContext;
/**
* Marked by {@link Options#metaRelease} and {@link Options#dryRun}.
*/
public class MetaReleaseDryRunCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -70;
private static final Logger log = LoggerFactory
.getLogger(MetaReleaseDryRunCompositeTask.class);
private final ApplicationContext context;
private DryRunCompositeTask dryRunCompositeTask;
public MetaReleaseDryRunCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "metaReleaseDryRun";
}
@Override
public String shortName() {
return "xdr";
}
@Override
public String header() {
return "META RELEASE DRY RUN";
}
@Override
public String description() {
return "Perform a meta release dry run of projects";
}
@Override
public ExecutionResult runTask(Arguments args) {
return dryRunCompositeTask().apply(args);
}
@Override
public void setup(Options options, ReleaserProperties properties) {
properties.getGit().setFetchVersionsFromGit(false);
properties.getMetaRelease().setEnabled(true);
options.fullRelease = true;
options.dryRun = true;
}
@Override
public int getOrder() {
return MetaReleaseDryRunCompositeTask.ORDER;
}
private DryRunCompositeTask dryRunCompositeTask() {
if (this.dryRunCompositeTask == null) {
this.dryRunCompositeTask = this.context.getBean(DryRunCompositeTask.class);
}
return this.dryRunCompositeTask;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.spring.FlowRunner;
import releaser.internal.spring.ProjectToRun;
import releaser.internal.spring.ProjectsToRun;
import releaser.internal.spring.TasksToRun;
import releaser.internal.tasks.CompositeReleaserTask;
import releaser.internal.tasks.ProjectPostReleaseReleaserTask;
import releaser.internal.tasks.ReleaseReleaserTask;
import releaser.internal.tasks.ReleaserTask;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Marked by {@link Options#fullRelease}.
*/
public class ReleaseCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -100;
private static final Logger log = LoggerFactory.getLogger(ReleaseCompositeTask.class);
private final ApplicationContext context;
private FlowRunner flowRunner;
public ReleaseCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "release";
}
@Override
public String shortName() {
return "fr";
}
@Override
public String header() {
return "FULL RELEASE";
}
@Override
public String description() {
return "Perform a full release of this project without interruptions";
}
@Override
public ExecutionResult runTask(Arguments args) {
Map<String, ReleaseReleaserTask> releaseTasks = this.context
.getBeansOfType(ReleaseReleaserTask.class);
Map<String, ProjectPostReleaseReleaserTask> projectPostReleaseTasks = this.context
.getBeansOfType(ProjectPostReleaseReleaserTask.class);
Collection<ReleaserTask> allReleaseTasks = new LinkedList<>(
releaseTasks.values());
allReleaseTasks.addAll(projectPostReleaseTasks.values());
List<ReleaserTask> values = new LinkedList<>(allReleaseTasks);
values.sort(AnnotationAwareOrderComparator.INSTANCE);
log.info(
"For project [{}], found the following release and project post release tasks {}",
args.project.getName(),
values.stream().map(r -> r.getClass().getSimpleName())
.collect(Collectors.toCollection(LinkedList::new)));
return flowRunner().runReleaseTasks(args.options, args.properties,
new ProjectsToRun(new ProjectToRun.ProjectToRunSupplier(
args.originalVersion.projectName, () -> args.projectToRun)),
new TasksToRun(values));
}
@Override
public void setup(Options options, ReleaserProperties properties) {
options.fullRelease = true;
}
@Override
public int getOrder() {
return ReleaseCompositeTask.ORDER;
}
private FlowRunner flowRunner() {
if (this.flowRunner == null) {
this.flowRunner = this.context.getBean(FlowRunner.class);
}
return this.flowRunner;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.CompositeReleaserTask;
import org.springframework.context.ApplicationContext;
public class ReleaseVerboseCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -90;
private static final Logger log = LoggerFactory
.getLogger(ReleaseVerboseCompositeTask.class);
private final ApplicationContext context;
private ReleaseCompositeTask releaserCompositeTask;
public ReleaseVerboseCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "releaseVerbose";
}
@Override
public String shortName() {
return "r";
}
@Override
public String header() {
return "FULL VERBOSE RELEASE";
}
@Override
public String description() {
return "Perform a full release of this project in interactive mode (you'll be asked about skipping steps)";
}
@Override
public ExecutionResult runTask(Arguments args) {
return releaserCompositeTask().apply(args);
}
@Override
public void setup(Options options, ReleaserProperties properties) {
options.fullRelease = true;
options.interactive = true;
}
@Override
public int getOrder() {
return ReleaseVerboseCompositeTask.ORDER;
}
private ReleaseCompositeTask releaserCompositeTask() {
if (this.releaserCompositeTask == null) {
this.releaserCompositeTask = this.context.getBean(ReleaseCompositeTask.class);
}
return this.releaserCompositeTask;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.composite;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import releaser.internal.ReleaserProperties;
import releaser.internal.options.Options;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.spring.FlowRunner;
import releaser.internal.spring.TasksToRun;
import releaser.internal.tasks.CompositeReleaserTask;
import releaser.internal.tasks.ReleaserTask;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Marked by {@link Options#metaRelease} and
* {@link ReleaserProperties#isPostReleaseTasksOnly()}.
*/
public class TrainPostReleaseCompositeTask implements CompositeReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = -50;
private static final Logger log = LoggerFactory
.getLogger(TrainPostReleaseCompositeTask.class);
private final ApplicationContext context;
private FlowRunner flowRunner;
public TrainPostReleaseCompositeTask(ApplicationContext context) {
this.context = context;
}
@Override
public String name() {
return "postRelease";
}
@Override
public String shortName() {
return "pr";
}
@Override
public String header() {
return "POST RELEASE TASKS";
}
@Override
public String description() {
return "Perform post release tasks for this release without interruptions";
}
@Override
public ExecutionResult runTask(Arguments args) {
Map<String, TrainPostReleaseReleaserTask> trainPostReleaseReleaserTasks = this.context
.getBeansOfType(TrainPostReleaseReleaserTask.class);
List<ReleaserTask> values = new LinkedList<>(
trainPostReleaseReleaserTasks.values());
values.sort(AnnotationAwareOrderComparator.INSTANCE);
log.info("Found the following post release tasks {}", values);
return flowRunner().runPostReleaseTrainTasks(args.options, args.properties,
this.name(), new TasksToRun(values));
}
@Override
public void setup(Options options, ReleaserProperties properties) {
options.metaRelease = true;
properties.setPostReleaseTasksOnly(true);
}
@Override
public int getOrder() {
return TrainPostReleaseCompositeTask.ORDER;
}
private FlowRunner flowRunner() {
if (this.flowRunner == null) {
this.flowRunner = this.context.getBean(FlowRunner.class);
}
return this.flowRunner;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ProjectPostReleaseReleaserTask;
public class CloseMilestonesProjectPostReleaseTask
implements ProjectPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 70;
private final Releaser releaser;
public CloseMilestonesProjectPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "closeMilestone";
}
@Override
public String shortName() {
return "m";
}
@Override
public String header() {
return "CLOSING MILESTONE";
}
@Override
public String description() {
return "Close the milestone at Github";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.closeMilestone(args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return CloseMilestonesProjectPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class CreateTemplatesTrainPostReleaseTask implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 70;
private final Releaser releaser;
public CreateTemplatesTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "createTemplates";
}
@Override
public String shortName() {
return "t";
}
@Override
public String header() {
return "CREATING TEMPLATES";
}
@Override
public String description() {
return "Create email / blog / tweet etc. templates";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.createEmail(args.versionFromBom, args.projects);
this.releaser.createBlog(args.versionFromBom, args.projects);
this.releaser.createTweet(args.versionFromBom, args.projects);
this.releaser.createReleaseNotes(args.versionFromBom, args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return CreateTemplatesTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.tasks.ConditionalOnDefaultFlowEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnDefaultFlowEnabled
@ConditionalOnProperty(value = "releaser.skip-post-release-tasks", havingValue = "false",
matchIfMissing = true)
class PostReleaseTasksConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-github-milestones")
CloseMilestonesProjectPostReleaseTask closeMilestonesReleaseTask(Releaser releaser) {
return new CloseMilestonesProjectPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.template.enabled")
CreateTemplatesTrainPostReleaseTask createTemplatesTrainPostReleaseTask(
Releaser releaser) {
return new CreateTemplatesTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.run-updated-samples")
RunUpdatedSamplesTrainPostReleaseTask runUpdatedSamplesTrainPostReleaseTask(
Releaser releaser) {
return new RunUpdatedSamplesTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-all-test-samples")
UpdateAllTestSamplesTrainPostReleaseTask updateAllTestSamplesTrainPostReleaseTask(
Releaser releaser) {
return new UpdateAllTestSamplesTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-documentation-repo")
UpdateDocsRepositoryProjectPostReleaseTask updateDocsRepositoryProjectPostReleaseTask(
Releaser releaser) {
return new UpdateDocsRepositoryProjectPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-documentation-repo")
UpdateDocsRepositoryTrainPostReleaseTask updateDocsRepositoryTrainPostReleaseTask(
Releaser releaser) {
return new UpdateDocsRepositoryTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-guides-repo")
UpdateGuidesTrainPostReleaseTask updateGuidesTrainPostReleaseTask(Releaser releaser) {
return new UpdateGuidesTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-release-train-docs")
UpdateReleaseTrainDocsTrainPostReleaseTask updateReleaseTrainDocsTrainPostReleaseTask(
Releaser releaser) {
return new UpdateReleaseTrainDocsTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.git.update-release-train-wiki")
UpdateReleaseTrainWikiTrainPostReleaseTask updateReleaseTrainWikiTrainPostReleaseTask(
Releaser releaser) {
return new UpdateReleaseTrainWikiTrainPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.sagan.update-sagan")
UpdateSaganProjectPostReleaseTask updateSaganProjectPostReleaseTask(
Releaser releaser) {
return new UpdateSaganProjectPostReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("releaser.sagan.update-start-spring-io")
UpdateStartSpringIoTrainPostReleaseTask updateStartSpringIoTrainPostReleaseTask(
Releaser releaser) {
return new UpdateStartSpringIoTrainPostReleaseTask(releaser);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class RunUpdatedSamplesTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 120;
private final Releaser releaser;
public RunUpdatedSamplesTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "runUpdatedSample";
}
@Override
public String shortName() {
return "ud";
}
@Override
public String header() {
return "UPDATE AND RUN SAMPLES";
}
@Override
public String description() {
return "Updates the sample project with versions and runs samples";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.runUpdatedSamples(args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return RunUpdatedSamplesTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateAllTestSamplesTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 130;
private final Releaser releaser;
public UpdateAllTestSamplesTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateAllSamples";
}
@Override
public String shortName() {
return "ua";
}
@Override
public String header() {
return "UPDATE ALL SAMPLES WITH RELEASE TRAIN BUMPED VERSIONS";
}
@Override
public String description() {
return "Update all samples with release train bumped versions";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateAllSamples(args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateAllTestSamplesTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ProjectPostReleaseReleaserTask;
public class UpdateDocsRepositoryProjectPostReleaseTask
implements ProjectPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 105;
private final Releaser releaser;
public UpdateDocsRepositoryProjectPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateDocumentationForProject";
}
@Override
public String shortName() {
return "udp";
}
@Override
public String header() {
return "UPDATE DOCUMENTATION FOR PROJECT";
}
@Override
public String description() {
return "Updating documentation repository for a single project";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateDocumentationRepositoryForSingleProject(args.projects,
args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateDocsRepositoryProjectPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateDocsRepositoryTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 110;
private final Releaser releaser;
public UpdateDocsRepositoryTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateDocumentation";
}
@Override
public String shortName() {
return "ud";
}
@Override
public String header() {
return "UPDATE DOCUMENTATION FOR RELEASE TRAIN";
}
@Override
public String description() {
return "Updating documentation repository for a release train";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateDocumentationRepositoryForTrain(args.properties,
args.projects, args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateDocsRepositoryTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateGuidesTrainPostReleaseTask implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 80;
private final Releaser releaser;
public UpdateGuidesTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateGuides";
}
@Override
public String shortName() {
return "ug";
}
@Override
public String header() {
return "UPDATE GUIDES";
}
@Override
public String description() {
return "Updating Spring Guides";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateSpringGuides(args.versionFromBom, args.projects,
args.processedProjects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateGuidesTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateReleaseTrainDocsTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 120;
private final Releaser releaser;
public UpdateReleaseTrainDocsTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateReleaseTrainDocs";
}
@Override
public String shortName() {
return "ur";
}
@Override
public String header() {
return "UPDATE RELEASE TRAIN DOCS";
}
@Override
public String description() {
return "Updating release train documentation";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.generateReleaseTrainDocumentation(args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateReleaseTrainDocsTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateReleaseTrainWikiTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 140;
private final Releaser releaser;
public UpdateReleaseTrainWikiTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateReleaseTrainWiki";
}
@Override
public String shortName() {
return "uw";
}
@Override
public String header() {
return "UPDATE RELEASE TRAIN WIKI";
}
@Override
public String description() {
return "Update release train wiki page";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateReleaseTrainWiki(args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateReleaseTrainWikiTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ProjectPostReleaseReleaserTask;
public class UpdateSaganProjectPostReleaseTask implements ProjectPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 100;
private final Releaser releaser;
public UpdateSaganProjectPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateSagan";
}
@Override
public String shortName() {
return "g";
}
@Override
public String header() {
return "UPDATE SAGAN";
}
@Override
public String description() {
return "Updating Sagan with release info";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateSagan(args.project, args.versionFromBom, args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateSaganProjectPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.postrelease;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.TrainPostReleaseReleaserTask;
public class UpdateStartSpringIoTrainPostReleaseTask
implements TrainPostReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 90;
private final Releaser releaser;
public UpdateStartSpringIoTrainPostReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updateStartSpringIo";
}
@Override
public String shortName() {
return "us";
}
@Override
public String header() {
return "UPDATE START.SPRING.IO";
}
@Override
public String description() {
return "Updating start.spring.io";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateStartSpringIo(args.versionFromBom, args.projects);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdateStartSpringIoTrainPostReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.DryRunReleaseReleaserTask;
public class BuildProjectReleaseTask implements DryRunReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 10;
private final Releaser releaser;
public BuildProjectReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "build";
}
@Override
public String shortName() {
return "b";
}
@Override
public String header() {
return "BUILD PROJECT";
}
@Override
public String description() {
return "Build the project";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.buildProject(args.originalVersion, args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return BuildProjectReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ReleaseReleaserTask;
public class BumpBackToSnapshotReleaseTask implements ReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 50;
private final Releaser releaser;
public BumpBackToSnapshotReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "snapshots";
}
@Override
public String shortName() {
return "s";
}
@Override
public String header() {
return "REVERTING CHANGES & BUMPING VERSION (RELEASE ONLY)";
}
@Override
public String description() {
return "Go back to snapshots and bump originalVersion by patch";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.rollbackReleaseVersion(args.project, args.projects,
args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return BumpBackToSnapshotReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ReleaseReleaserTask;
public class CommitReleaseTask implements ReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 20;
private final Releaser releaser;
public CommitReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "commit";
}
@Override
public String shortName() {
return "c";
}
@Override
public String header() {
return "COMMITTING (ALL) AND PUSHING TAGS (NON-SNAPSHOTS)";
}
@Override
public String description() {
return "Commit, tag and push the tag";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.commitAndPushTags(args.project, args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return CommitReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ReleaseReleaserTask;
public class DeployArtifactsReleaseTask implements ReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 30;
private final Releaser releaser;
public DeployArtifactsReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "deploy";
}
@Override
public String shortName() {
return "d";
}
@Override
public String header() {
return "ARTIFACT DEPLOYMENT";
}
@Override
public String description() {
return "Deploy the artifacts";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.deploy(args.originalVersion, args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return DeployArtifactsReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ReleaseReleaserTask;
public class PublishDocsReleaseTask implements ReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 40;
private final Releaser releaser;
public PublishDocsReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "docs";
}
@Override
public String shortName() {
return "o";
}
@Override
public String header() {
return "PUBLISHING DOCS";
}
@Override
public String description() {
return "Publish the docs";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.publishDocs(args.originalVersion, args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return PublishDocsReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.ReleaseReleaserTask;
public class PushChangesReleaseTask implements ReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 60;
private final Releaser releaser;
public PushChangesReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "push";
}
@Override
public String shortName() {
return "p";
}
@Override
public String header() {
return "PUSHING CHANGES";
}
@Override
public String description() {
return "Push the commits";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.pushCurrentBranch(args.project);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return PushChangesReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.tasks.ConditionalOnDefaultFlowEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnDefaultFlowEnabled
class ReleaseTasksConfiguration {
@Bean
@ConditionalOnMissingBean
BuildProjectReleaseTask buildProjectReleaseTask(Releaser releaser) {
return new BuildProjectReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
BumpBackToSnapshotReleaseTask bumpBackToSnapshotReleaseTask(Releaser releaser) {
return new BumpBackToSnapshotReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
CommitReleaseTask commitReleaseTask(Releaser releaser) {
return new CommitReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
DeployArtifactsReleaseTask deployArtifactsReleaseTask(Releaser releaser) {
return new DeployArtifactsReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
PublishDocsReleaseTask publishDocsReleaseTask(Releaser releaser) {
return new PublishDocsReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
PushChangesReleaseTask pushChangesReleaseTask(Releaser releaser) {
return new PushChangesReleaseTask(releaser);
}
@Bean
@ConditionalOnMissingBean
UpdatingPomsReleaseTask updatingPomsReleaseTask(Releaser releaser) {
return new UpdatingPomsReleaseTask(releaser);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.tasks.release;
import releaser.internal.Releaser;
import releaser.internal.spring.Arguments;
import releaser.internal.spring.ExecutionResult;
import releaser.internal.tasks.DryRunReleaseReleaserTask;
public class UpdatingPomsReleaseTask implements DryRunReleaseReleaserTask {
/**
* Order of this task. The higher value, the lower order.
*/
public static final int ORDER = 0;
private final Releaser releaser;
public UpdatingPomsReleaseTask(Releaser releaser) {
this.releaser = releaser;
}
@Override
public String name() {
return "updatePoms";
}
@Override
public String shortName() {
return "u";
}
@Override
public String header() {
return "UPDATING VERSIONS";
}
@Override
public String description() {
return "Update versions from the BOM";
}
@Override
public ExecutionResult runTask(Arguments args) {
this.releaser.updateProjectFromBom(args.project, args.projects,
args.versionFromBom);
return ExecutionResult.success();
}
@Override
public int getOrder() {
return UpdatingPomsReleaseTask.ORDER;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.buildsystem;
import java.io.File;
import org.apache.maven.model.Model;
import releaser.internal.tech.PomReader;
/**
* @author Marcin Grzejszczak
*/
public class TestPomReader {
public Model readPom(File pom) {
return PomReader.readPom(pom);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.buildsystem;
import java.io.File;
import java.io.IOException;
import org.eclipse.jgit.util.FileUtils;
public final class TestUtils {
private TestUtils() {
throw new IllegalStateException("Can't instantiate a utility class");
}
public static void prepareLocalRepo() throws IOException {
prepareLocalRepo("target/test-classes/projects/", "spring-cloud");
prepareLocalRepo("target/test-classes/projects/", "spring-cloud-wiki");
prepareLocalRepo("target/test-classes/projects/", "spring-cloud-release");
prepareLocalRepo("target/test-classes/projects/",
"spring-cloud-release-with-snapshot");
prepareLocalRepo("target/test-classes/projects/", "spring-cloud-consul");
prepareLocalRepo("target/test-classes/projects/", "spring-cloud-build");
prepareLocalRepo("target/test-classes/projects/", "spring-cloud-static-angel");
}
private static void prepareLocalRepo(String buildDir, String repoPath)
throws IOException {
File dotGit = new File(buildDir + repoPath + "/.git");
File git = new File(buildDir + repoPath + "/git");
if (git.exists()) {
if (dotGit.exists()) {
FileUtils.delete(dotGit, FileUtils.RECURSIVE);
}
}
git.renameTo(dotGit);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.docs;
import edu.emory.mathcs.backport.java.util.Collections;
import releaser.internal.ReleaserProperties;
import releaser.internal.git.ProjectGitHandler;
/**
* @author Marcin Grzejszczak
*/
public class TestDocumentationUpdater extends DocumentationUpdater {
public TestDocumentationUpdater(ReleaserProperties properties,
CustomProjectDocumentationUpdater updater, ProjectGitHandler handler,
TestReleaseContentsUpdater testRelease) {
super(properties, new ProjectDocumentationUpdater(properties, handler,
Collections.singletonList(updater)), testRelease);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.docs;
import releaser.internal.ReleaserProperties;
import releaser.internal.git.ProjectGitHandler;
import releaser.internal.template.TemplateGenerator;
/**
* @author Marcin Grzejszczak
*/
public class TestReleaseContentsUpdater extends ReleaseTrainContentsUpdater {
public TestReleaseContentsUpdater(ReleaserProperties properties,
ProjectGitHandler handler, TemplateGenerator templateGenerator) {
super(properties, handler, templateGenerator);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.git;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.RemoteRemoveCommand;
import org.eclipse.jgit.api.RemoteSetUrlCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.URIish;
/**
* @author Marcin Grzejszczak
*/
public final class GitTestUtils {
private GitTestUtils() {
throw new IllegalStateException("Can't instantiate a utility class");
}
public static void setOriginOnProjectToTmp(File origin, File project)
throws GitAPIException, MalformedURLException {
try (Git git = openGitProject(project)) {
RemoteRemoveCommand remove = git.remoteRemove();
remove.setName("origin");
remove.call();
RemoteSetUrlCommand command = git.remoteSetUrl();
command.setUri(new URIish(origin.toURI().toURL()));
command.setName("origin");
command.setPush(true);
command.call();
}
}
public static Git openGitProject(File project) {
return new GitRepo.JGitFactory().open(project);
}
public static File clonedProject(File baseDir, File projectToClone)
throws IOException {
GitRepo projectRepo = new GitRepo(baseDir);
return projectRepo.cloneProject(new URIish(projectToClone.toURI().toURL()));
}
}

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
/**
* @author Marcin Grzejszczak
*/
// @RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("unchecked")
public class OptionsProcessorTests {
}
/*
* @Mock Releaser releaser;
*
* FirstConsumer first = new FirstConsumer();
*
* SecondConsumer second = new SecondConsumer();
*
* ThirdConsumer third = new ThirdConsumer();
*
* Task firstTask = task("first", "1", "", "", this.first);
*
* List<Task> tasks = Arrays .asList(new Task[] { this.firstTask, task("second", "2", "",
* "", this.second), task("third", "3", "", "", this.third) });
*
* OptionsProcessor optionsProcessor;
*
* static Task task(String name, String shortName, String header, String description,
* Consumer<Args> function) { return new Task(name, shortName, header, description,
* function); }
*
* @Before public void setup() { this.optionsProcessor = new
* OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks); Task.stepSkipper
* = () -> false; }
*
* @After public void clean() { Task.stepSkipper = new ConsoleInputStepSkipper(); }
*
* @Test public void should_throw_exception_when_an_invalid_option_was_picked() { Options
* options = nonInteractiveOpts().options();
*
* thenThrownBy(() -> this.optionsProcessor.processOptions(options, args()))
* .hasMessageContaining("You haven't picked any recognizable option"); }
*
* @Test public void should_execute_only_tasks_after_the_provided_one_using_full_name() {
* Options options = nonInteractiveOpts().startFrom("second").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_only_tasks_after_the_provided_one_using_short_name() {
* Options options = nonInteractiveOpts().startFrom("2").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_only_tasks_from_range_using_full_name() { Options
* options = nonInteractiveOpts().range("second-third").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_only_tasks_from_range_using_short_name() { Options
* options = nonInteractiveOpts().range("2-3").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isTrue(); }
*
* @Test public void
* should_execute_only_tasks_from_range_using_full_name_with_same_range() { Options
* options = nonInteractiveOpts().range("second-second").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isFalse(); }
*
* @Test public void
* should_execute_only_tasks_from_range_using_short_name_with_same_range() { Options
* options = nonInteractiveOpts().range("2-2").options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isFalse(); }
*
* @Test public void should_execute_only_tasks_from_multi_using_full_name() { Options
* options = nonInteractiveOpts().taskNames(list("first", "third")) .options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_only_tasks_from_multi_using_short_name() { Options
* options = nonInteractiveOpts().taskNames(list("1", "3")).options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_interactively_only_single_task() {
* this.optionsProcessor = new OptionsProcessor(this.releaser, new ReleaserProperties(),
* this.tasks) {
*
* @Override String chosenOption() { return "0"; } }; Options options =
* interactiveOpts().options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isFalse(); }
*
* @Test public void should_execute_interactively_range_of_tasks() { this.optionsProcessor
* = new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
*
* @Override String chosenOption() { return "0-1"; } }; Options options =
* interactiveOpts().options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isTrue();
* then(this.third.executed).isFalse(); }
*
* @Test public void should_execute_interactively_start_from() { this.optionsProcessor =
* new OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
*
* @Override String chosenOption() { return "1-"; } }; Options options =
* interactiveOpts().options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isFalse(); then(this.second.executed).isTrue();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_interactively_multi() { this.optionsProcessor = new
* OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
*
* @Override String chosenOption() { return "0,2"; } }; Options options =
* interactiveOpts().options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isTrue(); }
*
* @Test public void should_execute_full_release() { this.optionsProcessor = new
* OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
*
* @Override Task releaseTask() { return OptionsProcessorTests.this.firstTask; }
*
* @Override String chosenOption() { return "0"; } }; Options options =
* nonInteractiveOpts().fullRelease(true).options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isFalse(); }
*
* @Test public void should_execute_full_verbose_release() { this.optionsProcessor = new
* OptionsProcessor(this.releaser, new ReleaserProperties(), this.tasks) {
*
* @Override Task releaseVerboseTask() { return OptionsProcessorTests.this.firstTask; }
*
* @Override String chosenOption() { return "0"; } }; Options options =
* interactiveOpts().fullRelease(true).options();
*
* this.optionsProcessor.processOptions(options, args());
*
* then(this.first.executed).isTrue(); then(this.second.executed).isFalse();
* then(this.third.executed).isFalse(); }
*
* @Test public void should_remove_single_quotes() { Options options =
* interactiveOpts().fullRelease(true).range("'1-2'")
* .startFrom("'c'").taskNames(Arrays.asList("'a'", "'b'")).options();
*
* then(options.range).isEqualTo("1-2"); then(options.startFrom).isEqualTo("c");
* then(options.taskNames).containsOnly("a", "b"); }
*
* private OptionsBuilder interactiveOpts() { return new OptionsBuilder(); }
*
* private OptionsBuilder nonInteractiveOpts() { return new
* OptionsBuilder().interactive(false); }
*
* private Args args() { return new Args(null, null, null, null, null, null, false,
* TaskType.RELEASE, null); }
*
* private List<String> list(String... list) { return Arrays.asList(list); }
*
* }
*
* class FirstConsumer implements Consumer<Args> {
*
* boolean executed;
*
* @Override public void accept(Args o) { this.executed = true; }
*
* }
*
* class SecondConsumer implements Consumer<Args> {
*
* boolean executed;
*
* @Override public void accept(Args o) { this.executed = true; }
*
* }
*
* class ThirdConsumer implements Consumer<Args> {
*
* boolean executed;
*
* @Override public void accept(Args o) { this.executed = true; }
*
* }
*/

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.File;
import java.net.URL;
import java.util.List;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import releaser.internal.ReleaserProperties;
import releaser.internal.ReleaserPropertiesAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Marcin Grzejszczak
*/
@RunWith(SpringRunner.class)
@Import(ReleaserPropertiesIntegrationTests.Config.class)
public class ReleaserPropertiesIntegrationTests {
@Autowired
List<ReleaserPropertiesAware> propertiesAware;
@Autowired
ApplicationContext context;
@Test
public void should_update_properties() {
ReleaserProperties properties = new ReleaserProperties();
properties.getPom().setBranch("fooooo");
new ReleaserPropertiesUpdater(this.context).updateProperties(properties,
new File("."));
BDDAssertions.then(this.propertiesAware).hasSize(2);
this.propertiesAware.forEach(aware -> BDDAssertions
.then(((ReleaserPropertiesHaving) aware).properties.getPom().getBranch())
.isEqualTo("fooooo"));
}
@Test
public void should_update_properties_including_existing_releaser_config() {
ReleaserProperties properties = new ReleaserProperties();
properties.getPom().setBranch("barrrr");
URL resource = ReleaserPropertiesIntegrationTests.class
.getResource("/projects/project-with-config");
new ReleaserPropertiesUpdater(this.context).updateProperties(properties,
new File(resource.getFile()));
BDDAssertions.then(this.propertiesAware).hasSize(2);
this.propertiesAware.forEach(aware -> {
ReleaserPropertiesHaving having = ((ReleaserPropertiesHaving) aware);
BDDAssertions.then(having.properties.getPom().getBranch())
.isEqualTo("barrrr");
BDDAssertions.then(having.properties.getMaven().getBuildCommand())
.isEqualTo("./scripts/noIntegration.sh");
});
}
@Test
public void should_update_properties_including_existing_releaser_config_for_netflix() {
ReleaserProperties properties = new ReleaserProperties();
properties.getPom().setBranch("bazzzz");
URL resource = ReleaserPropertiesIntegrationTests.class
.getResource("/projects/project-with-netflix-config");
new ReleaserPropertiesUpdater(this.context).updateProperties(properties,
new File(resource.getFile()));
BDDAssertions.then(this.propertiesAware).hasSize(2);
this.propertiesAware.forEach(aware -> {
ReleaserPropertiesHaving having = ((ReleaserPropertiesHaving) aware);
BDDAssertions.then(having.properties.getPom().getBranch())
.isEqualTo("bazzzz");
BDDAssertions.then(having.properties.getMaven().getBuildCommand())
.isEqualTo("./scripts/build.sh {{systemProps}}");
});
}
@Configuration
static class Config {
@Bean
ReleaserPropertiesAware aware1() {
return new ReleaserPropertiesHaving();
}
@Bean
ReleaserPropertiesAware aware2() {
return new ReleaserPropertiesHaving();
}
}
static class ReleaserPropertiesHaving implements ReleaserPropertiesAware {
ReleaserProperties properties;
@Override
public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
ReleaserProperties getProps() {
return this.properties;
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.io.File;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.mockito.BDDMockito;
import releaser.internal.ReleaserProperties;
import releaser.internal.ReleaserPropertiesAware;
import org.springframework.context.ApplicationContext;
/**
* @author Marcin Grzejszczak
*/
public class ReleaserPropertiesUpdaterTests {
ApplicationContext context = BDDMockito.mock(ApplicationContext.class);
File relaserUpdater;
public ReleaserPropertiesUpdaterTests() throws URISyntaxException {
this.relaserUpdater = new File(ReleaserPropertiesUpdaterTests.class
.getResource("/projects/releaser-updater/").toURI());
}
@Test
public void should_update_properties() {
ReleaserProperties original = originalReleaserProperties();
Aware aware = new Aware();
BDDMockito.given(this.context.getBeansOfType(BDDMockito.any(Class.class)))
.willReturn(beansOfType(aware));
ReleaserPropertiesUpdater updater = new ReleaserPropertiesUpdater(this.context);
ReleaserProperties props = updater.updateProperties(original,
this.relaserUpdater);
BDDAssertions.then(aware.properties).isNotNull();
BDDAssertions.then(props.getMaven().getBuildCommand()).isEqualTo("maven_build");
BDDAssertions.then(props.getGradle().getBuildCommand()).isEqualTo("gradle_build");
BDDAssertions.then(props.getBash().getBuildCommand()).isEqualTo("bash_build");
BDDAssertions.then(props.getMaven().getSystemProperties()).isEqualTo("-Dfoo=bar");
}
ReleaserProperties originalReleaserProperties() {
ReleaserProperties props = new ReleaserProperties();
props.getMaven().setSystemProperties("-Dfoo=bar");
return props;
}
private Map<String, Object> beansOfType(Aware aware) {
Map<String, Object> map = new HashMap<>();
map.put("foo", aware);
return map;
}
class Aware implements ReleaserPropertiesAware {
ReleaserProperties properties;
@Override
public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 releaser.internal.spring;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.Test;
import releaser.internal.ReleaserProperties;
class SpringBatchFlowRunnerTests {
@Test
void releaseGroup() {
ReleaserProperties properties = new ReleaserProperties();
properties.getMetaRelease().setReleaseGroups(Arrays.asList("c,d", "e,f,g"));
ProjectsToRun projectsToRun = projectsToRun();
List<ReleaseGroup> groups = new ProjectsToReleaseGroups(properties)
.toReleaseGroup(projectsToRun);
BDDAssertions.then(groups).hasSize(5);
BDDAssertions.then(projectNames(0, groups)).containsExactly("a");
BDDAssertions.then(projectNames(1, groups)).containsExactly("b");
BDDAssertions.then(projectNames(2, groups)).containsExactly("d", "c");
BDDAssertions.then(projectNames(3, groups)).containsExactly("e", "f", "g");
BDDAssertions.then(projectNames(4, groups)).containsExactly("h");
}
private List<String> projectNames(int index, List<ReleaseGroup> groups) {
return groups.get(index).projectsToRun.stream()
.map(ProjectToRun.ProjectToRunSupplier::projectName)
.collect(Collectors.toCollection(LinkedList::new));
}
private ProjectsToRun projectsToRun() {
ProjectsToRun projectsToRun = new ProjectsToRun();
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("a", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("b", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("d", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("c", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("e", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("f", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("g", () -> null));
projectsToRun.add(new ProjectToRun.ProjectToRunSupplier("h", () -> null));
return projectsToRun;
}
}

View File

@@ -0,0 +1,79 @@
spring:
main:
web-application-type: none
datasource:
url: jdbc:h2:mem:${random.uuid}
jackson:
deserialization:
FAIL_ON_UNKNOWN_PROPERTIES: true
releaser:
git:
release-train-bom-url: https://github.com/spring-cloud/spring-cloud-release
documentation-url: https://github.com/spring-cloud/spring-cloud-static
spring-project-url: https://github.com/spring-projects/spring-cloud
test-samples-project-url: https://github.com/spring-cloud/spring-cloud-core-tests
release-train-docs-url: https://github.com/spring-cloud-samples/scripts
release-train-wiki-url: https://github.com/spring-projects/spring-cloud.wiki
documentation-branch: gh-pages
spring-project-branch: gh-pages
test-samples-branch: master
release-train-docs-branch: master
release-train-wiki-page-prefix: Spring-Cloud
fetch-versions-from-git: true
oauth-token:
# username:
# password:
update-documentation-repo: true
update-github-milestones: true
update-spring-guides: true
update-start-spring-io: true
update-spring-project: true
run-updated-samples: true
update-release-train-docs: true
update-release-train-wiki: true
update-all-test-samples: true
all-test-sample-urls:
spring-cloud-sleuth:
- https://github.com/spring-cloud-samples/sleuth-issues
- https://github.com/spring-cloud-samples/sleuth-documentation-apps
spring-cloud-contract:
- https://github.com/spring-cloud-samples/spring-cloud-contract-samples
- https://github.com/spring-cloud-samples/the-legacy-app
- https://github.com/spring-cloud-samples/sc-contract-car-rental
pom:
branch: master
pom-with-boot-starter-parent: spring-cloud-starter-parent/pom.xml
this-train-bom: spring-cloud-dependencies/pom.xml
bom-version-pattern: "^(spring-cloud-.*)\\.version$"
ignored-pom-regex:
- "^.*\\.git/.*$"
- "^.*spring-cloud-contract-maven-plugin/src/test/projects/.*$"
- "^.*spring-cloud-contract-maven-plugin/target/.*$"
- "^.*src/test/bats/.*$"
- "^.*samples/standalone/[a-z]+/.*$"
sagan:
update-sagan: true
base-url: https://spring.io
docs-adocs-file: docs/src/main/asciidoc
index-section-file-name: sagan-index.adoc
boot-section-file-name: sagan-boot.adoc
template:
enabled: true
template-folder: cloud
versions:
all-versions-file-url: https://raw.githubusercontent.com/spring-io/start.spring.io/master/start-site/src/main/resources/application.yml
bom-name: spring-cloud
# fixed-versions:
meta-release:
enabled: false
release-train-project-name: spring-cloud-release
release-train-dependency-names:
- spring-cloud
- spring-cloud-dependencies
- spring-cloud-starter
- spring-cloud-starter-build
git-org-url: https://github.com/spring-cloud
projects-to-skip:
- spring-boot
- spring-cloud-stream
- spring-cloud-task

View File

@@ -0,0 +1,4 @@
releaser.maven.buildCommand: ./scripts/noIntegration.sh
releaser.gradle.gradlePropsSubstitution:
verifierVersion: spring-cloud-contract
bootVersion: spring-boot

View File

@@ -0,0 +1,3 @@
releaser:
maven:
buildCommand: ./scripts/build.sh {{systemProps}}

View File

@@ -0,0 +1,6 @@
releaser.maven.buildCommand: maven_build
releaser.bash.buildCommand: bash_build
releaser.gradle.buildCommand: gradle_build
releaser.gradle.gradlePropsSubstitution:
verifierVersion: spring-cloud-contract
bootVersion: spring-boot