├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .mvn ├── maven.config └── extensions.xml ├── images └── screenshot.png ├── .gitignore ├── src ├── main │ ├── resources │ │ ├── index.jelly │ │ └── io │ │ │ └── jenkins │ │ │ └── plugins │ │ │ └── addchangestobuildchangelog │ │ │ └── AddChangesToBuildChangelogStep │ │ │ └── config.jelly │ └── java │ │ └── io │ │ └── jenkins │ │ └── plugins │ │ └── addchangestobuildchangelog │ │ ├── CustomChangeSet.java │ │ ├── CustomChangePath.java │ │ ├── CustomChange.java │ │ ├── AddChangesToBuildChangelogStep.java │ │ └── AddChangesToBuildChangelog.java └── test │ └── java │ └── io │ └── jenkins │ └── plugins │ └── addchangestobuildchangelog │ └── AddChangesToBuildChangelogTest.java ├── Jenkinsfile ├── LICENSE.md ├── README.md └── pom.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/add-changes-to-build-changelog-plugin-developers 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/add-changes-to-build-changelog-plugin/main/images/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # mvn hpi:run 4 | work 5 | 6 | # IntelliJ IDEA project files 7 | *.iml 8 | *.iws 9 | *.ipr 10 | .idea 11 | 12 | # Eclipse project files 13 | .settings 14 | .classpath 15 | .project 16 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This plugin was aimed at adding changes to a Jenkins build that didn't come from SCM information. Maybe the changes were calculated outside of Jenkins, but someone wanted to show those changes in the Jenkins UI. 4 |
5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: maven 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.5 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/addchangestobuildchangelog/AddChangesToBuildChangelogStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores 7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | ]) 12 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/addchangestobuildchangelog/CustomChangeSet.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import java.util.List; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Json Object for a collection of changes. 8 | */ 9 | public class CustomChangeSet { 10 | private List changes; 11 | public CustomChangeSet( 12 | List changes) { 13 | this.changes = changes; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | List lines = new ArrayList(); 19 | for(CustomChange change : changes) { 20 | lines.add(change.toString()); 21 | } 22 | 23 | return String.join("\n", lines); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2023 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/addchangestobuildchangelog/CustomChangePath.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | 5 | /** 6 | * Json Object for a path inside a change. 7 | */ 8 | public class CustomChangePath { 9 | private String path; 10 | private boolean modified; 11 | private boolean added; 12 | private boolean deleted; 13 | 14 | public CustomChangePath( 15 | String path, 16 | boolean modified, 17 | boolean added, 18 | boolean deleted) { 19 | this.path = path; 20 | this.modified = modified; 21 | this.added = added; 22 | this.deleted = deleted; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | // not really for sure what this corresponds to, but I didn't need it for my purposes 28 | // update if you need it 29 | String uniqueString = RandomStringUtils.randomNumeric(40); 30 | String prefix = ""; 31 | if(modified) { 32 | prefix = "M"; 33 | } else if(added) { 34 | prefix = "A"; 35 | } else if(deleted) { 36 | prefix = "D"; 37 | } 38 | 39 | return ":000000 000000 " + uniqueString + " " + uniqueString + " " + prefix + "\t" + path; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/addchangestobuildchangelog/CustomChange.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import java.util.List; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Json Object for a single change. 8 | */ 9 | public class CustomChange { 10 | private String commit; 11 | private String author; 12 | private String email; 13 | private String message; 14 | private String date; 15 | private List paths; 16 | 17 | public CustomChange( 18 | String commit, 19 | String author, 20 | String email, 21 | String date, 22 | String message, 23 | List paths) { 24 | this.commit = commit; 25 | this.author = author; 26 | this.email = email; 27 | this.message = message; 28 | this.date = date; 29 | this.paths = paths; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | List lines = new ArrayList(); 35 | lines.add("commit " + commit); 36 | lines.add("author " + author + " <" + email + "> " + date); 37 | lines.add("committer " + author + " <" + email + "> " + date); 38 | lines.add(""); 39 | lines.add(" " + message); 40 | lines.add(""); 41 | lines.add(""); 42 | lines.add(getPathStrings()); 43 | return String.join("\n", lines); 44 | } 45 | 46 | private String getPathStrings() { 47 | List lines = new ArrayList(); 48 | for(CustomChangePath path : paths) { 49 | lines.add(path.toString()); 50 | } 51 | 52 | return String.join("\n", lines); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/addchangestobuildchangelog/AddChangesToBuildChangelogStep.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import hudson.Launcher; 4 | import hudson.EnvVars; 5 | import hudson.Extension; 6 | import hudson.FilePath; 7 | import hudson.util.FormValidation; 8 | import hudson.model.AbstractProject; 9 | import hudson.model.Run; 10 | import hudson.model.TaskListener; 11 | import hudson.tasks.Builder; 12 | import hudson.Util; 13 | import hudson.tasks.BuildStepDescriptor; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | import org.kohsuke.stapler.QueryParameter; 16 | import org.kohsuke.stapler.DataBoundSetter; 17 | import org.kohsuke.stapler.AncestorInPath; 18 | import jenkins.tasks.SimpleBuildStep; 19 | import java.io.IOException; 20 | import java.lang.StackTraceElement; 21 | import org.jenkinsci.Symbol; 22 | import java.lang.IllegalArgumentException; 23 | 24 | /** 25 | * Build step for adding Custom Changes to the Build Changelog via String or File. 26 | */ 27 | public class AddChangesToBuildChangelogStep extends Builder implements SimpleBuildStep { 28 | /** 29 | * Path to the Changelog in the workspace. 30 | */ 31 | private final String changelogPath; 32 | 33 | /** 34 | * String version of the changelog. 35 | */ 36 | private final String changelogText; 37 | 38 | @DataBoundConstructor 39 | public AddChangesToBuildChangelogStep( 40 | String changelogPath, 41 | String changelogText) { 42 | this.changelogPath = changelogPath; 43 | this.changelogText = changelogText; 44 | } 45 | 46 | @Override 47 | public DescriptorImpl getDescriptor() { 48 | return (DescriptorImpl)super.getDescriptor(); 49 | } 50 | 51 | public String getChangelogText() { 52 | return changelogText; 53 | } 54 | 55 | public String getChangelogPath() { 56 | return changelogPath; 57 | } 58 | 59 | private void printException(Exception e, TaskListener listener) { 60 | listener.getLogger().println(e.getMessage()); 61 | for(StackTraceElement trace : e.getStackTrace()) { 62 | listener.getLogger().println(trace.toString()); 63 | } 64 | } 65 | 66 | @Override 67 | public void perform( 68 | Run run, 69 | FilePath workspace, 70 | EnvVars env, 71 | Launcher launcher, 72 | TaskListener listener) throws InterruptedException, IOException { 73 | try { 74 | // If we didn't define a string version, read in the workspace file. 75 | String text = Util.fixEmptyAndTrim(changelogText); 76 | if(text == null) { 77 | text = workspace.child(changelogPath).readToString(); 78 | } 79 | 80 | // Perform the addition. 81 | AddChangesToBuildChangelog addChanges = new AddChangesToBuildChangelog(); 82 | addChanges.perform(text, run, workspace, listener); 83 | 84 | listener.getLogger().println("Done"); 85 | } catch(Exception e) { 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | 90 | @Symbol("addchangestobuildchangelog") 91 | @Extension 92 | public static final class DescriptorImpl extends BuildStepDescriptor { 93 | @Override 94 | public String getDisplayName() { 95 | return "Add Changes to Build Changelog"; 96 | } 97 | 98 | @Override 99 | public boolean isApplicable(Class aClass) { 100 | return true; 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://ci.jenkins.io/job/Plugins/job/add-changes-to-build-changelog-plugin/job/main/badge/icon)](https://ci.jenkins.io/job/Plugins/job/add-changes-to-build-changelog-plugin/job/main/) 2 | [![cd](https://github.com/jenkinsci/add-changes-to-build-changelog-plugin/actions/workflows/cd.yaml/badge.svg)](https://github.com/jenkinsci/add-changes-to-build-changelog-plugin/actions/workflows/cd.yaml) 3 | 4 | # Add Changes to Build Changelog Plugin 5 | 6 | ## About 7 | 8 | This plugin was aimed at adding changes to a Jenkins build that didn't come from SCM information. Maybe the changes were calculated outside of Jenkins, but someone wanted to show those changes in the Jenkins UI. 9 | 10 | ## Target Audience 11 | 12 | Stack Overflow had a lot of questions regarding adding custom changes to the Jenkins change log, but there was no easy way to do this. Most relied on knowing the Jenkins internals and scripting. Thus, this plugin was created to help. 13 | 14 | For example, here are some of those questions: 15 | 16 | [Jenkins - Updating build changelog during build step](https://stackoverflow.com/questions/14047974) 17 | 18 | [Is there a way to set/change the changeSet (changelog) content from pipeline script? Needed for preflight type of job](https://stackoverflow.com/questions/60565782) 19 | 20 | [Generating Custom GitSCM Changelog in Jenkins Pipeline](https://stackoverflow.com/questions/42810248) 21 | 22 | [insert custom changelog to Jenkins job](https://stackoverflow.com/questions/44126901) 23 | 24 | [Jenkins: Modify the ChangeSet List to have changes since last successful build](https://stackoverflow.com/questions/72911958) 25 | 26 | [Jenkins Plugin for Writing to the Change Log?](https://stackoverflow.com/questions/26530840) 27 | 28 | ## Usage 29 | 30 | - Freestyle Jobs 31 | - Pipeline 32 | 33 | ## Pipeline Builds 34 | 35 | > **Note** 36 | > Internally, anything you add to the build will be treated as a Git change. 37 | 38 | Example pipeline script: 39 | ``` 40 | node { 41 | // IMPORTANT: If you want to add new lines to the message part, you will need to tack on a few spaces after each new line. 42 | // In my case, it was 4 spaces (e.g. "First Line\n Second Line\n Third Line") 43 | def text = ''' 44 | { 45 | "changes": [ 46 | { 47 | "commit": "364cd043160d870b655f66c5925b18b9e14961e1", 48 | "author": "Jane Doe", 49 | "email": "jane@doe.com", 50 | "date": "2023-02-03 07:30:05 +0000", 51 | "message": "My git change.", 52 | "paths": [ 53 | { "path": "hello.txt", "modified": true, "deleted": false, "added": false } 54 | ] 55 | } 56 | ] 57 | } 58 | ''' 59 | 60 | // Read in the changes via text 61 | addchangestobuildchangelog changelogText: text 62 | 63 | // Read in the changes via file path 64 | writeFile file: 'changelog.txt', text: text 65 | addchangestobuildchangelog changelogPath: 'changelog.txt' 66 | } 67 | ``` 68 | 69 | ## Freestyle Builds 70 | 71 | > **Note** 72 | > At the moment, you can only specify Git SCM as the default checkout type. The other SCM types (like SVN, etc) are not support yet. If you want to use this plugin and do not have a git repository, then specify an empty git repository to get around that restriction. 73 | 74 | Screenshot: 75 | 76 | ![](images/screenshot.png) 77 | 78 | Example git changelog text: 79 | ``` 80 | { 81 | "changes": [ 82 | { 83 | "commit": "364cd043160d870b655f66c5925b18b9e14961e1", 84 | "author": "Jane Doe", 85 | "email": "jane@doe.com", 86 | "date": "2023-02-03 07:30:05 +0000", 87 | "message": "My git change.", 88 | "paths": [ 89 | { "path": "hello.txt", "modified": true, "deleted": false, "added": false } 90 | ] 91 | } 92 | ] 93 | } 94 | ``` 95 | 96 | ## Contributing 97 | 98 | Any and all contributions welcome! 99 | 100 | ## License 101 | 102 | Licensed under MIT, see [LICENSE](LICENSE.md) 103 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 4.52 8 | 9 | 10 | io.jenkins.plugins 11 | add-changes-to-build-changelog 12 | ${changelist} 13 | hpi 14 | Add Changes to Build Changelog 15 | https://github.com/jenkinsci/${project.artifactId}-plugin 16 | 17 | 18 | MIT License 19 | https://opensource.org/licenses/MIT 20 | 21 | 22 | 23 | scm:git:https://github.com/${gitHubRepo} 24 | scm:git:https://github.com/${gitHubRepo} 25 | ${scmTag} 26 | https://github.com/${gitHubRepo} 27 | 28 | 999999-SNAPSHOT 29 | 30 | 31 | 2.361 32 | ${jenkins.baseline}.4 33 | jenkinsci/${project.artifactId}-plugin 34 | 35 | 36 | 37 | 38 | 39 | io.jenkins.tools.bom 40 | bom-${jenkins.baseline}.x 41 | 1723.vcb_9fee52c9fc 42 | pom 43 | import 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.jenkins-ci.plugins 51 | structs 52 | 53 | 54 | org.jenkins-ci.plugins.workflow 55 | workflow-cps 56 | test 57 | 58 | 59 | org.jenkins-ci.plugins.workflow 60 | workflow-basic-steps 61 | test 62 | 63 | 64 | 65 | org.jenkins-ci.plugins.workflow 66 | workflow-job 67 | 68 | 69 | 70 | org.jenkins-ci.plugins 71 | git 72 | 73 | 74 | 75 | org.mockito 76 | mockito-core 77 | 3.3.0 78 | test 79 | 80 | 81 | 82 | commons-io 83 | commons-io 84 | 2.11.0 85 | 86 | 87 | 88 | com.google.code.gson 89 | gson 90 | 2.8.9 91 | 92 | 93 | 94 | io.jenkins.plugins 95 | commons-lang3-api 96 | 97 | 98 | 99 | 100 | 101 | repo.jenkins-ci.org 102 | https://repo.jenkins-ci.org/public/ 103 | 104 | 105 | 106 | 107 | repo.jenkins-ci.org 108 | https://repo.jenkins-ci.org/public/ 109 | 110 | 111 | 112 | 113 | 114 | maven-surefire-plugin 115 | org.apache.maven.plugins 116 | 117 | all 118 | true 119 | 1C 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/addchangestobuildchangelog/AddChangesToBuildChangelogTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import hudson.model.FreeStyleBuild; 4 | import hudson.model.FreeStyleProject; 5 | import hudson.model.Label; 6 | import hudson.scm.ChangeLogSet; 7 | import hudson.model.Result; 8 | import hudson.model.Cause; 9 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 11 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.jvnet.hudson.test.JenkinsRule; 15 | import org.junit.Assert; 16 | import java.util.List; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.text.DecimalFormat; 20 | import java.io.IOException; 21 | import hudson.plugins.git.GitSCM; 22 | import com.google.gson.Gson; 23 | 24 | /** 25 | * Test cases for adding Custom Changes to the Build Changelog via String or File. 26 | */ 27 | public class AddChangesToBuildChangelogTest { 28 | @Rule 29 | public JenkinsRule jenkins = new JenkinsRule(); 30 | 31 | /** 32 | * Test case where someone tries to add custom changes to a Freestyle that doesn't have SCM defined. 33 | * This should result in an exception and build failure. 34 | */ 35 | @Test 36 | public void testFreestyleBuildWithNoScm() throws IOException, Exception { 37 | FreeStyleProject project = jenkins.createFreeStyleProject(); 38 | AddChangesToBuildChangelogStep builder = new AddChangesToBuildChangelogStep(null, getChangeLogText()); 39 | project.getBuildersList().add(builder); 40 | FreeStyleBuild build = project.scheduleBuild2(0, new Cause.UserIdCause()).get(); 41 | jenkins.assertBuildStatus(Result.FAILURE, build); 42 | jenkins.assertLogContains("Scm cannot be None", build); 43 | } 44 | 45 | /** 46 | * Test case where someone tries to add custom changes to a Freestyle that has Git SCM defined. 47 | * This should add the changes to the Build Changelog. 48 | */ 49 | @Test 50 | public void testFreestyleBuildWithGitScm() throws IOException, Exception { 51 | // We need to checkout something at the beginning. 52 | // Let's use an empty repo I created for this purpose. 53 | GitSCM scm = new GitSCM("https://github.com/danielomoto/emptyrepo.git"); 54 | FreeStyleProject project = jenkins.createFreeStyleProject(); 55 | project.setScm(scm); 56 | 57 | // create our builder passing in the text version of 3 git commits. 58 | AddChangesToBuildChangelogStep builder = new AddChangesToBuildChangelogStep(null, getChangeLogText()); 59 | project.getBuildersList().add(builder); 60 | FreeStyleBuild completedBuild = jenkins.buildAndAssertSuccess(project); 61 | 62 | // Test no failure. 63 | jenkins.assertLogContains("Done", completedBuild); 64 | 65 | // Make sure there are 3 commits in the changeSets. 66 | Assert.assertEquals(3, ((ChangeLogSet)completedBuild.getChangeSets().toArray()[0]).getItems().length); 67 | } 68 | 69 | /** 70 | * Test case where someone tries to add custom changes to a pipeline. 71 | * This should add the changes to the Build Changelog. 72 | */ 73 | @Test 74 | public void testSupportedScriptedPipelineWithCommits() throws IOException, Exception { 75 | String agentLabel = "my-agent"; 76 | jenkins.createOnlineSlave(Label.get(agentLabel)); 77 | WorkflowJob job = jenkins.createProject(WorkflowJob.class, "test-scripted-pipeline"); 78 | // Create the pipeline script and assign the entire changelog to the text variable. 79 | // Note that I'm using triple quotes to allow the string to span multiple lines. 80 | String pipelineScript 81 | = "node {\n" 82 | + " def text = \"\"\"" + getChangeLogText() + "\"\"\"\n" 83 | + " addchangestobuildchangelog changelogText: text\n" 84 | + "}"; 85 | job.setDefinition(new CpsFlowDefinition(pipelineScript, true)); 86 | WorkflowRun completedBuild = jenkins.assertBuildStatusSuccess(job.scheduleBuild2(0)); 87 | 88 | // Test no failure. 89 | jenkins.assertLogContains("Done", completedBuild); 90 | 91 | // Make sure there are 3 commits in the changeSets. 92 | Assert.assertEquals(3, ((ChangeLogSet)completedBuild.getChangeSets().toArray()[0]).getItems().length); 93 | } 94 | 95 | /** 96 | * Helper class to create 3 bogus commits in a changelog string 97 | */ 98 | private String getChangeLogText() throws Exception { 99 | List commits = new ArrayList(); 100 | 101 | commits.add(new CustomChange( 102 | "659d00186d94581c05283afefe885a4ad5a186a8", 103 | "Joe Smith", 104 | "joe@smith.com", 105 | "2023-02-01 13:35:03 +0530", 106 | "Hello", 107 | Arrays.asList( 108 | new CustomChangePath("/home/test.jpg", false, false, true), 109 | new CustomChangePath("/var/log.txt", false, true, false), 110 | new CustomChangePath("/user/foo.png", true, false, false) 111 | ) 112 | )); 113 | commits.add(new CustomChange( 114 | "5fa9113196c54518a3f91bbe2bfac46842af9cac", 115 | "Jane Doe", 116 | "jane@doe.com", 117 | "2024-03-02 14:46:14 +0530", 118 | "Bonjour", 119 | new ArrayList() 120 | )); 121 | commits.add(new CustomChange( 122 | "26dede03e5dd169b0ccf5c0e5b9bc4bb9cbafdf2", 123 | "Richard Doctor", 124 | "richard@doctor.com", 125 | "2024-03-02 14:46:14 +0530", 126 | "Konnichiwa", 127 | Arrays.asList( 128 | new CustomChangePath("/soul/sister.png", true, false, false), 129 | new CustomChangePath("/holy/cow.png", true, false, false) 130 | ) 131 | )); 132 | 133 | CustomChangeSet set = new CustomChangeSet(commits); 134 | Gson gson = new Gson(); 135 | return gson.toJson(set); 136 | } 137 | } -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/addchangestobuildchangelog/AddChangesToBuildChangelog.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.addchangestobuildchangelog; 2 | 3 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 4 | import hudson.util.Secret; 5 | import hudson.Util; 6 | import hudson.model.TaskListener; 7 | import hudson.plugins.git.GitSCM; 8 | import hudson.FilePath; 9 | import hudson.model.Run; 10 | import hudson.model.FreeStyleBuild; 11 | import java.util.List; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.text.DecimalFormat; 16 | import java.util.UUID; 17 | import java.lang.IllegalArgumentException; 18 | import org.apache.commons.io.FileUtils; 19 | import hudson.scm.SCM; 20 | import com.google.gson.Gson; 21 | import java.lang.ref.WeakReference; 22 | import hudson.model.AbstractBuild; 23 | import java.lang.reflect.Field; 24 | import hudson.scm.ChangeLogParser; 25 | import hudson.scm.ChangeLogSet; 26 | 27 | /** 28 | * Implementation for adding Custom Changes to the Build Changelog via String or File. 29 | */ 30 | public class AddChangesToBuildChangelog { 31 | /** 32 | * Entry point. 33 | */ 34 | public void perform( 35 | String json, 36 | Run run, 37 | FilePath workspace, 38 | TaskListener listener) throws Exception, IOException, IllegalArgumentException { 39 | String text = getChangeLogText(json); 40 | if(run instanceof WorkflowRun) { 41 | perfromAgainstWorkflowRun(text, (WorkflowRun)run, workspace, listener); 42 | } else if(run instanceof FreeStyleBuild) { 43 | perfromAgainstFreeStyleBuild(text, (FreeStyleBuild)run, workspace, listener); 44 | } else { 45 | throw new IllegalArgumentException("Only Pipeline and Freestyle jobs are supported at this time."); 46 | } 47 | } 48 | 49 | /** 50 | * Creates a changelog file with a random postfix and writes it to the build directory. 51 | */ 52 | private String outputChangeLog(String text, WorkflowRun run) throws IOException { 53 | String buildDir = run.getArtifactsDir().getPath().replace("archive", ""); 54 | String changelogPath = buildDir + "changelog" + UUID.randomUUID().toString(); 55 | File f = new File(changelogPath); 56 | FileUtils.writeStringToFile(f, text); 57 | return changelogPath; 58 | } 59 | 60 | /** 61 | * Performs the necessary logic against Pipeline jobs. 62 | */ 63 | private void perfromAgainstWorkflowRun( 64 | String text, 65 | WorkflowRun run, 66 | FilePath workspace, 67 | TaskListener listener) throws IOException, Exception { 68 | String changelogPath = outputChangeLog(text, run); 69 | org.jenkinsci.plugins.workflow.job.WorkflowRun.SCMListenerImpl scmListenerImpl = new org.jenkinsci.plugins.workflow.job.WorkflowRun.SCMListenerImpl(); 70 | scmListenerImpl.onCheckout(run, new GitSCM(""), workspace, listener, new File(changelogPath), null); 71 | } 72 | 73 | /** 74 | * Helpwer class to append the text to the preexisting changelog.xml file. 75 | */ 76 | private File appendChangeLog(String text, FreeStyleBuild run) throws IOException { 77 | String buildDir = run.getArtifactsDir().getPath().replace("archive", ""); 78 | String changelogPath = buildDir + "changelog.xml"; 79 | // WARNING: The text you're appending must match what's defined in the configure screen. 80 | // Otherwise, nothing will show up. 81 | File f = new File(changelogPath); 82 | if(f.exists()) { 83 | text = FileUtils.readFileToString(f, "utf-8") + "\n" + text; 84 | } 85 | 86 | FileUtils.writeStringToFile(f, text); 87 | return f; 88 | } 89 | 90 | /** 91 | * Performs the necessary logic against Freestyle Builds. 92 | */ 93 | private void perfromAgainstFreeStyleBuild( 94 | String text, 95 | FreeStyleBuild run, 96 | FilePath workspace, 97 | TaskListener listener) throws IOException, IllegalArgumentException, Exception { 98 | SCM scm = run.getProject().getScm(); 99 | // Due to the way Freestyle builds work, they already define and do a checkout before any build steps run. 100 | // Thus, we can't just add changes to the changelog without making sure it matches what was defined earlier. 101 | // For Scm types of None, we can't add changes unless we change the configuration, which is way beyond what 102 | // this small plugin is going to do. 103 | if(scm instanceof hudson.scm.NullSCM) { 104 | throw new IllegalArgumentException("Scm cannot be None"); 105 | } else if(!(scm instanceof GitSCM)) { 106 | throw new IllegalArgumentException("Only Git SCM is supported."); 107 | } 108 | 109 | // It seems like just appending or creating a new changelog.xml is all that's needed for FreestyleBuilds. 110 | // The configuration must get reloaded after the build step completes or something. 111 | File changeLog = appendChangeLog(text, run); 112 | 113 | // Just updating the changelog.xml didn't work for Linux (however it did work for Windows). 114 | // To fix both cases, we need to tell the FreeStyle to reload it's changelog.xml from disk. 115 | reloadFreeStyleChangeSet(run, changeLog); 116 | } 117 | 118 | /** 119 | * This will reload the FreeStyle changelog from disk. 120 | */ 121 | private void reloadFreeStyleChangeSet(FreeStyleBuild run, File changeLog) throws Exception { 122 | Field changeSet = AbstractBuild.class.getDeclaredField("changeSet"); 123 | Field scm = AbstractBuild.class.getDeclaredField("scm"); 124 | 125 | changeSet.setAccessible(true); 126 | scm.setAccessible(true); 127 | 128 | ChangeLogSet cls = ((ChangeLogParser)scm.get(run)).parse(run, changeLog); 129 | changeSet.set(run, new WeakReference<>(cls)); 130 | } 131 | 132 | /** 133 | * Convert changes json into a git changelog string 134 | */ 135 | private String getChangeLogText(String json) { 136 | Gson gson = new Gson(); 137 | CustomChangeSet changes = gson.fromJson(json, CustomChangeSet.class); 138 | return changes.toString(); 139 | } 140 | } 141 | --------------------------------------------------------------------------------