├── .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 extends AbstractProject> aClass) {
100 | return true;
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.jenkins.io/job/Plugins/job/add-changes-to-build-changelog-plugin/job/main/)
2 | [](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 | 
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 |
--------------------------------------------------------------------------------