Skip to content

Commit

Permalink
[FIXED JENKINS-29134]
Browse files Browse the repository at this point in the history
Added support for automatically overriding the values of "BUILDS_TODAY",
"BUILDS_THIS_MONTH", "BUILDS_THIS_YEAR" and "BUILDS_ALL_TIME" with
values taken from environment-variables.

Instead of just providing a simple number in the form-fields of the
job's plugin-configuration which overrides the value for the next build,
one can now provide an environment-variable whose value will be
extracted and used instead during the next builds.
If it is not set or its value is not convertible to a positive integer,
the value of the previous build will be taken instead and increased by
one.

Signed-off-by: Deniz Bahadir <dbahadir@benocs.com>
  • Loading branch information
Bagira80 committed Jun 30, 2015
1 parent 786ffe3 commit 5a669b5
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 75 deletions.
Expand Up @@ -21,6 +21,8 @@
import java.util.Date;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.json.JSONObject;

Expand All @@ -39,25 +41,35 @@
* <p>
* This plugin keeps track of its version through a {@link VersionNumberAction} attached
* to the project. Each build that uses this plugin has its own VersionNumberAction,
* and this contains the builds today/this month/ this year/ all time. When incrementing
* and this contains the builds today / this month / this year / all time. When incrementing
* each of these values, unless they're overridden in the configuration the value from
* the previous build will be used.
* </p>
* <p>
* Such a value can be either overridden with a plain number or with an environment-variable.
* In the later case the value will be read from the environment-variable at build-time. If
* it cannot be parsed as an integer the value from the previous build will be incremented
* and used instead.
* </p>
*
* @author Carl Lischeske - NETFLIX
* @author Deniz Bahadir - BENOCS
*/
public class VersionNumberBuilder extends BuildWrapper {

private static final DateFormat defaultDateFormat = new SimpleDateFormat("yyyy-MM-dd");
// Pattern: ${VAR_NAME} or $VAR_NAME
private static final String envVarPattern = new String("^(?:\\$\\{(\\w+)\\})|(?:\\$(\\w+))$");

private final String versionNumberString;
private final Date projectStartDate;
private final String environmentVariableName;
private final String environmentPrefixVariable;

private int oBuildsToday;
private int oBuildsThisMonth;
private int oBuildsThisYear;
private int oBuildsAllTime;
private String oBuildsToday;
private String oBuildsThisMonth;
private String oBuildsThisYear;
private String oBuildsAllTime;

private boolean skipFailedBuilds;
private boolean useAsBuildDisplayName;
Expand Down Expand Up @@ -94,43 +106,26 @@ public VersionNumberBuilder(String versionNumberString,
this.skipFailedBuilds = skipFailedBuilds;
this.useAsBuildDisplayName = useAsBuildDisplayName;

try {
oBuildsToday = Integer.parseInt(buildsToday);
} catch (Exception e) {
oBuildsToday = -1;
}
try {
oBuildsThisMonth = Integer.parseInt(buildsThisMonth);
} catch (Exception e) {
oBuildsThisMonth = -1;
}
try {
oBuildsThisYear = Integer.parseInt(buildsThisYear);
} catch (Exception e) {
oBuildsThisYear = -1;
}
try {
oBuildsAllTime = Integer.parseInt(buildsAllTime);
} catch (Exception e) {
oBuildsAllTime = -1;
}

this.oBuildsToday = makeValid(buildsToday);
this.oBuildsThisMonth = makeValid(buildsThisMonth);
this.oBuildsThisYear = makeValid(buildsThisYear);
this.oBuildsAllTime = makeValid(buildsAllTime);
}

public String getBuildsToday() {
return "";
return this.oBuildsToday;
}

public String getBuildsThisMonth() {
return "";
return this.oBuildsThisMonth;
}

public String getBuildsThisYear() {
return "";
return this.oBuildsThisYear;
}

public String getBuildsAllTime() {
return "";
return this.oBuildsAllTime;
}

public boolean getSkipFailedBuilds() {
Expand All @@ -149,6 +144,40 @@ private static Date parseDate(String dateString) {
}
}

/**
* Checks if the given string contains a valid value and returns that
* value again if it is valid or returns an empty string if it is not. A
* valid value encoded in the string must either be a (positive) number,
* convertible to an integer or a reference to an environment-variable in
* the form <code>${VARIABLE_NAME}</code> or <code>$VARIABLE_NAME</code>.
* @param buildNum The (user-provided) string which should either contain
* a number or a reference to an environment-variable.
* @return The given <a>buildNum</a> if valid or an empty string.
*/
private static String makeValid(String buildNum) {
try {
// If we got a valid integer the following conversion will
// succeed without an exception.
Integer intVal = new Integer(buildNum);
if (intVal < 0)
return ""; // Negative numbers are not allowed.
else
return intVal.toString();
} catch (Exception e) {
// Obviously, we did not receive a valid integer as override.
// Is it a reference to an environment-variable?
if (buildNum != null && buildNum.matches(envVarPattern)) {
// Yes, so return it as-is and only retrieve its value when
// the value must be accessed (to always get the most
// up-to-date value).
return buildNum;
} else {
// No, so it seems to be junk. Just return the default-value.
return "";
}
}
}

/**
* We'll use this from the <tt>config.jelly</tt>.
*/
Expand All @@ -166,48 +195,48 @@ public String getEnvironmentPrefixVariable() {
return this.environmentPrefixVariable;
}
private Run getPreviousBuildWithVersionNumber(AbstractBuild build) {
String envPrefix;

if (this.environmentPrefixVariable != null) {
try {
EnvVars env = build.getEnvironment(null);

envPrefix = env.get(this.environmentPrefixVariable);
} catch (IOException e) {
envPrefix = null;
} catch (InterruptedException e) {
envPrefix = null;
}
} else {
envPrefix = null;
}

String envPrefix;

if (this.environmentPrefixVariable != null) {
try {
EnvVars env = build.getEnvironment(null);

envPrefix = env.get(this.environmentPrefixVariable);
} catch (IOException e) {
envPrefix = null;
} catch (InterruptedException e) {
envPrefix = null;
}
} else {
envPrefix = null;
}

// a build that fails early will not have a VersionNumberAction attached
Run prevBuild = build.getPreviousBuild();


while (prevBuild != null) {
VersionNumberAction prevAction = (VersionNumberAction)prevBuild.getAction(VersionNumberAction.class);


if (prevAction != null) {
if (envPrefix != null) {
String version = prevAction.getVersionNumber();

if (version.startsWith(envPrefix)) {
return prevBuild;

if (version.startsWith(envPrefix)) {
return prevBuild;
}
} else {
return prevBuild;
}
}


prevBuild = prevBuild.getPreviousBuild();
}


return null;
}

@SuppressWarnings("unchecked")
private VersionNumberBuildInfo incBuild(AbstractBuild build, PrintStream log) throws IOException {
private VersionNumberBuildInfo incBuild(AbstractBuild build, Map<String, String> enVars, PrintStream log) throws IOException {
Run prevBuild = getPreviousBuildWithVersionNumber(build);
int buildsToday = 1;
int buildsThisMonth = 1;
Expand Down Expand Up @@ -259,28 +288,96 @@ private VersionNumberBuildInfo incBuild(AbstractBuild build, PrintStream log) th
// increment total builds
buildsAllTime = info.getBuildsAllTime() + buildInc;
}

// have we overridden any of the version number info? If so, set it up here
boolean saveOverrides = false;
if (this.oBuildsToday >= 0) {
buildsToday = oBuildsToday;
oBuildsToday = -1;
saveOverrides = true;
Pattern pattern = Pattern.compile(envVarPattern);

if (!this.oBuildsToday.equals("")) {
saveOverrides = true; // Always need to save if not empty!
// Just in case someone directly edited the config-file with invalid values.
oBuildsToday = makeValid(oBuildsToday);
int newVal = buildsToday;
try {
if (!oBuildsToday.matches(envVarPattern)) {
newVal = Integer.parseInt(oBuildsToday);
oBuildsToday = ""; // Reset!
} else {
Matcher m = pattern.matcher(oBuildsToday);
if (m.matches()) {
String varName = (m.group(1) != null) ? m.group(1) : m.group(2);
newVal = Integer.parseInt(enVars.get(varName));
}
}
} catch (Exception e) {
// Invalid value, so do not override!
}
buildsToday = ((newVal >= 0) ? newVal : buildsToday);
}
if (this.oBuildsThisMonth >= 0) {
buildsThisMonth = oBuildsThisMonth;
oBuildsThisMonth = -1;
saveOverrides = true;
if (!this.oBuildsThisMonth.equals("")) {
saveOverrides = true; // Always need to save if not empty!
// Just in case someone directly edited the config-file with invalid values.
oBuildsThisMonth = makeValid(oBuildsThisMonth);
int newVal = buildsThisMonth;
try {
if (!oBuildsThisMonth.matches(envVarPattern)) {
newVal = Integer.parseInt(oBuildsThisMonth);
oBuildsThisMonth = ""; // Reset!
} else {
Matcher m = pattern.matcher(oBuildsThisMonth);
if (m.matches()) {
String varName = (m.group(1) != null) ? m.group(1) : m.group(2);
newVal = Integer.parseInt(enVars.get(varName));
}
}
} catch (Exception e) {
// Invalid value, so do not override!
}
buildsThisMonth = ((newVal >= 0) ? newVal : buildsThisMonth);
}
if (this.oBuildsThisYear >= 0) {
buildsThisYear = oBuildsThisYear;
oBuildsThisYear = -1;
saveOverrides = true;
if (!this.oBuildsThisYear.equals("")) {
saveOverrides = true; // Always need to save if not empty!
// Just in case someone directly edited the config-file with invalid values.
oBuildsThisYear = makeValid(oBuildsThisYear);
int newVal = buildsThisYear;
try {
if (!oBuildsThisYear.matches(envVarPattern)) {
newVal = Integer.parseInt(oBuildsThisYear);
oBuildsThisYear = ""; // Reset!
} else {
Matcher m = pattern.matcher(oBuildsThisYear);
if (m.matches()) {
String varName = (m.group(1) != null) ? m.group(1) : m.group(2);
newVal = Integer.parseInt(enVars.get(varName));
}
}
} catch (Exception e) {
// Invalid value, so do not override!
}
buildsThisYear = ((newVal >= 0) ? newVal : buildsThisYear);
}
if (this.oBuildsAllTime >= 0) {
buildsAllTime = oBuildsAllTime;
oBuildsAllTime = -1;
saveOverrides = true;
if (!this.oBuildsAllTime.equals("")) {
saveOverrides = true; // Always need to save if not empty!
// Just in case someone directly edited the config-file with invalid values.
oBuildsAllTime = makeValid(oBuildsAllTime);
int newVal = buildsAllTime;
try {
if (!oBuildsAllTime.matches(envVarPattern)) {
newVal = Integer.parseInt(oBuildsAllTime);
oBuildsAllTime = ""; // Reset!
} else {
Matcher m = pattern.matcher(oBuildsAllTime);
if (m.matches()) {
String varName = (m.group(1) != null) ? m.group(1) : m.group(2);
newVal = Integer.parseInt(enVars.get(varName));
}
}
} catch (Exception e) {
// Invalid value, so do not override!
}
buildsAllTime = ((newVal >= 0) ? newVal : buildsAllTime);
}

// if we've used any of the overrides, reset them in the project
if (saveOverrides) {
build.getProject().save();
Expand Down Expand Up @@ -395,7 +492,7 @@ private static String sizeTo(String s, int length) {
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) {
String formattedVersionNumber = "";
try {
VersionNumberBuildInfo info = incBuild(build, listener.getLogger());
VersionNumberBuildInfo info = incBuild(build, build.getEnvironment(listener), listener.getLogger());
formattedVersionNumber = formatVersionNumber(this.versionNumberString,
this.projectStartDate,
info,
Expand Down
21 changes: 17 additions & 4 deletions src/main/webapp/help-overrideNumbers.html
@@ -1,6 +1,19 @@
<div>
If a number is specified here, it will override the number tracked from build to build and the next build
will use this number instead. If left blank, it will increment the number from the previous build if necessary,
and use that. If no previous build used the Version Number Plugin, then this defaults to 1. Negative numbers
are not allowed.
<p>
If left blank, the next build will increment the number from the previous build if necessary,
and use that. If no previous build used the Version Number Plugin, then this defaults to 1.
</p>
<p>
If a number is specified here, it will override the number tracked from build to build and the
next build will use this number instead. Negative numbers are not allowed and will be ignored.
</p>
<p>
If an environment-variable is specified here, its value will be retrieved during the next build
and will be used as number. However, if that environment-variable is not set or its value is not
a (positive) number, the standard behavior will kick in as if this field was left blank. (That
means, the number from the previous build will be incremented and used.)
</p>
<p>
Other values will be ignored as if left blank.
</p>
</div>
Expand Up @@ -111,6 +111,25 @@ public void testUseAsBuildDisplayName() throws Exception {
assertEquals("1.0.2", build.getDisplayName());
}

public void testValueFromEnvironmentVariable() throws Exception {
FreeStyleProject job = createFreeStyleProject("versionNumberJob");
VersionNumberBuilder versionNumberBuilder = new VersionNumberBuilder(
"${BUILDS_TODAY}.${BUILDS_THIS_MONTH}.${BUILDS_THIS_YEAR}.${BUILDS_ALL_TIME}",
null, null, null, "${ENVVAL_OF_TODAY}", "${ENVVAL_OF_THIS_MONTH}", "${ENVVAL_OF_THIS_YEAR}", "${ENVVAL_OF_ALL_TIME}", false, true);

EnvironmentVariablesNodeProperty prop = new EnvironmentVariablesNodeProperty();
EnvVars envVars = prop.getEnvVars();
envVars.put("ENVVAL_OF_TODAY", "-10"); // Invalid (negative) value
envVars.put("ENVVAL_OF_THIS_MONTH", "Invalid"); // Invalid (non-number) value
//envVars.put("ENVVAL_OF_THIS_YEAR", ""); // No variable
envVars.put("ENVVAL_OF_ALL_TIME", "20"); // Normal value
super.hudson.getGlobalNodeProperties().add(prop);

job.getBuildWrappersList().add(versionNumberBuilder);
FreeStyleBuild build = buildAndAssertSuccess(job);
assertEquals("1.1.1.20", build.getDisplayName());
}

private void assertBuildsAllTime(int expected, AbstractBuild build) {
VersionNumberAction versionNumberAction = build
.getAction(VersionNumberAction.class);
Expand Down

0 comments on commit 5a669b5

Please sign in to comment.