Webapp versions v1.0

You should be able to determine which version of your program is running.

I think this can be illustrated by giving an example of two versions of a program:

One of these downloads is a multi-megabyte OpenGL-based warfare simulation in which theories of warfare can be tested and refined without the need for actual hostilities, including not only military but also political and social factors, seen as inextricably entwined in a realistic warfare model [1].

The other is a multi-megabyte package that will display the following dialog box:

mystery-application.exe is not a valid Win32 application. OR IS IT?
mystery-application.exe is not a valid Win32 application. OR IS IT?

These are, of course, two versions of the exact same piece of software; the only difference between them is that in one I’ve converted the PE COFF machine identifier to be a 7.

What I’m attempting to illustrate here is how extraordinarily brittle software is as a whole, and how knowing which version of an application an end-user is running is fairly significant when attempting to determine what the problem is at any particular point in time [2] [3].

It’s also an illustration of how pissed off I get when my State Government decides to spend over a billion Australian dollars on replacing a payroll system that worked perfectly well, but apparently no-one knew how to add a new employee to it or something. This stuff doesn’t rust, people.

Software artifacts are different from physical artifacts. In nature, you can usually break a twig without killing the tree, but this isn’t true of software, most instances of which will fall into a heap if you breathe on them the wrong way. Some developers attempt to minimise these ‘single points of failure’ by creating hideously complex design patterns, but I should probably leave that rant for another time.

Anyway, where was I.

You should be able to determine which version of your program is running.

Some people believe that you will receive 72 versions after you go jihadding. They should work in IT. Ahahaha.

It’s usually the first question that any IT support person will ask, because if you don’t know what version you’re running, then you don’t have a baseline from which to work out what to change to fix whatever problem you’re having [3].

If you were the sort of person who over-dramatises things, you can think of it as being similar to the fire brigade asking what building you’re in when you ring them up complaining that the air is a bit thick today. Just before they enter your address into some kind of computer system. Which is definitely working.

Microsoft desktop applications normally shove this under the Help → About dialog, command-line programs normally have a -v or -? switch, but webapps don’t have a standard for it.

Microsoft also believes that you should store version numbers in content type headers, meta tags, document type declarations, and four or five places in the registry, but let’s ignore that for the time being.

“Yes”, you may say, “but everyone using a webapp is by definition using the same version of the webapp”, and well you might, you handsome devil you, but you haven’t considered internally-deployed webapps, or worked in a overly-bureaucratic or security-conscious organisation that considers that kind of information too difficult to retrieve or confidential, and you don’t have hundreds of the bloody things scattered over the net.

I normally allow the version information to be retrieved from /context-root/version (where context-root is usually just “/” under the root domain), which returns a JSON map with the following keys:

  • bamboo.buildKey
  • bamboo.buildNumber
  • bamboo.buildPlanName
  • bamboo.buildTimeStamp
  • bamboo.buildForceCleanCheckout
  • bamboo.custom.cvs.last.update.time
  • bamboo.custom.cvs.last.update.time.label
  • maven.pom.name
  • maven.pom.version
  • maven.pom.groupId
  • maven.pom.artifactId

where the bamboo.xxxx keys return their equivalent Bamboo global variable values, and the maven.xxxx keys return their equivalent maven implicit property values.

You can, for example, see what happens when you click on www.randomnoun.com/version, which will tell you which version this site is currently running.

The bamboo version data is useful for DEV and TST environments, where I normally run snapshot artifacts, and the maven version data is useful for XPT and PRD environments, where I normally run full releases.

Having this information available online means that vmaint can draw the following pretty diagrams, and I can be fairly confident that it’s up-to-date:

Current state of the eomail-web and dmx-web webapps in various environments
Current state of the eomail-web and dmx-web webapps in various environments

which also provides sufficient information to query source control and provide a list of changes that will be brought in when you move one version of the webapp into a new environment:

Cropped representation of the differences between dmx-web in TST (build 93) and dmx-web in XPT (build 47)
Cropped representation of the differences between dmx-web in TST (build 93) and dmx-web in XPT (build 47). These are the changes that will be migrated if the TST build is migrated to XPT.
Each bullet point represents a checkin into CVS, the green ticks represent successful bamboo builds and the red exclamation mark represents a failed bamboo build.

The versioning process is part of the maven build process, and has the following moving parts:

  • /src/main/resoures/build.properties
  • com.randomnoun.common.servlet.VersionServlet
  • /pom.xml fragment to filter the build.properties file
  • /src/main/webapp/WEB-INF/web.xml to configure the servlet

Here is the contents of the build.properties file, which should should be placed in the /src/main/resources folder relative to the project base directory. When the project is built by maven, the expressions in ${var} form are replaced with placeholders supplied by bamboo and maven:

build.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# property file containing keys passed in from bamboo
#   see http://confluence.atlassian.com/display/BAMBOO/Using+Global+or+Build-specific+Variables
# some of the properties below require Bamboo 2.6
# you will need this in the bamboo maven goal specification:
#
# clean deploy 
# "-DbambooBuildKey=${bamboo.buildKey}" 
# "-DbambooBuildNumber=${bamboo.buildNumber}" 
# "-DbambooBuildPlanName=${bamboo.buildPlanName}" 
# "-DbambooBuildTimeStamp=${bamboo.buildTimeStamp}" 
# "-DbambooBuildForceCleanCheckout=${bamboo.buildForceCleanCheckout}" 
# "-DbambooCustomCvsLastUpdateTime=${bamboo.custom.cvs.last.update.time}" 
# "-DbambooCustomCvsLastUpdateTimeLabel=${bamboo.custom.cvs.last.update.time.label}" 
# -DuniqueVersion=false
 
bamboo.buildKey=${bambooBuildKey}
bamboo.buildNumber=${bambooBuildNumber}
bamboo.buildPlanName=${bambooBuildPlanName}
bamboo.buildTimeStamp=${bambooBuildTimeStamp}
bamboo.buildForceCleanCheckout=${bambooForceCleanCheckout}
bamboo.custom.cvs.last.update.time=${bambooCustomCvsLastUpdateTime}
bamboo.custom.cvs.last.update.time.label=${bambooCustomCvsLastUpdateTimeLabel}
 
maven.pom.name=${pom.name}
maven.pom.version=${pom.version}
maven.pom.groupId=${pom.groupId}
maven.pom.artifactId=${pom.artifactId}

And this is the VersionServlet source, which reads the filtered form of this file and reports the values in a JSON format:

VersionServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.randomnoun.common.servlet;
 
/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
 */
 
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.log4j.Logger;
import org.json.JSONObject;
 
/**
 * Reads the contents of the build.properties file in the 
 * current application and sends it to the browser in a 
 * variety of forms. Probably JSON for starters.
 *
 * <p>The properties file must be located on the classpath at the
 * location "/build.properties".
 * 
 * <p>For this to work, the application needs to have been built
 * through maven (and possibly bamboo), and there should be a 
 * <tt>build.properties</tt> file in the application being created, 
 * with the contents:
 * 
 * <pre>
 *  bamboo.buildKey=${bambooBuildKey}
	bamboo.buildNumber=${bambooBuildNumber}
	bamboo.buildPlanName=${bambooBuildPlanName}
	bamboo.buildTimeStamp=${bambooBuildTimeStamp}
	bamboo.buildForceCleanCheckout=${bambooForceCleanCheckout}
	bamboo.custom.cvs.last.update.time=${bambooCustomCvsLastUpdateTime}
	bamboo.custom.cvs.last.update.time.label=${bambooCustomCvsLastUpdateTimeLabel}
 
	maven.pom.name=${pom.name}
    maven.pom.version=${pom.version}
    maven.pom.groupId=${pom.groupId}
    maven.pom.artifactId=${pom.artifactId}
   </pre>
 *
 * and the bamboo plan should have the following in the bamboo plan's
 * stage's job's task's maven goal specification:
 * 
 * <pre>
 *  "-DbambooBuildKey=${bamboo.buildKey}" 
    "-DbambooBuildNumber=${bamboo.buildNumber}" 
    "-DbambooBuildPlanName=${bamboo.buildPlanName}" 
    "-DbambooBuildTimeStamp=${bamboo.buildTimeStamp}" 
    "-DbambooBuildForceCleanCheckout=${bamboo.buildForceCleanCheckout}" 
    "-DbambooCustomCvsLastUpdateTime=${bamboo.custom.cvs.last.update.time}" 
    "-DbambooCustomCvsLastUpdateTimeLabel=${bamboo.custom.cvs.last.update.time.label}" 
    -DuniqueVersion=false 
 * </pre>
 * 
 * @author knoxg
 * @blog http://www.randomnoun.com/wp/2013/09/24/webapp-versions-v1-0/
 * @version $Id: VersionServlet.java,v 1.1 2013-09-24 02:37:00 knoxg Exp $
 */
@SuppressWarnings("serial")
public class VersionServlet extends HttpServlet {
 
	/** A revision marker to be used in exception stack traces. */
    public static final String _revision = "$Id: VersionServlet.java,v 1.1 2013-09-24 02:37:00 knoxg Exp $";
 
	/** Logger for this class */
    public static final Logger logger = Logger.getLogger(VersionServlet.class);
 
 
	/** Post method; just defers to get
	 * 
	 * @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
 
	/** If a property has not been populated through the maven resource filter
	 * mechanism, then remove it from the Properties object.
	 *   
	 * @param props Properties object
	 * @param key key to check
	 * @param value default value
	 */
	private void removeDefaultProperty(Properties props, String key, String value) {
		if (("${" + value + "}").equals(props.get(key))) {
			props.remove(key);
		}
	}
 
	/** See class documentation
	 * 
	 * @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
    	InputStream is = VersionServlet.class.getClassLoader().getResourceAsStream("/build.properties");
    	Properties props = new Properties();
    	if (is==null) {
    		props.put("error", "Missing build.properties");
    	} else {
	    	props.load(is);
	    	is.close();
    	}
 
    	removeDefaultProperty(props, "bamboo.buildKey", "bambooBuildKey");
    	removeDefaultProperty(props, "bamboo.buildNumber", "bambooBuildNumber");
    	removeDefaultProperty(props, "bamboo.buildPlanName", "bambooBuildPlanName");
    	removeDefaultProperty(props, "bamboo.buildTimeStamp", "bambooBuildTimeStamp");
    	removeDefaultProperty(props, "bamboo.buildForceCleanCheckout", "bambooForceCleanCheckout");
    	removeDefaultProperty(props, "bamboo.custom.cvs.last.update.time", "bambooCustomCvsLastUpdateTime");
    	removeDefaultProperty(props, "bamboo.custom.cvs.last.update.time.label", "bambooCustomCvsLastUpdateTimeLabel");
 
    	response.setHeader("Content-Type", "application/json");
    	response.setStatus(HttpServletResponse.SC_OK);
    	response.getWriter().println(new JSONObject(props).toString());
    }
}

pom.xml fragment

In order to transform the build.properties into it’s filtered form, you will need this in your pom.xml:

<project>
  ... 
  <build>
    ...
    <resources>
      <resource>
        <filtering>true</filtering>
        <directory>src/main/resources</directory>
        <includes>
          <include>build.properties</include>
        </includes>
      </resource>
      <resource>
        <filtering>false</filtering>
        <directory>src/main/resources</directory>
        <excludes>
          <exclude>build.properties</exclude>
        </excludes>
      </resource>
    </resources>
    ...
  </build>
  ...
</project>

web.xml fragment

And in order for the VersionServlet to respond to the “/version” URI, you will need this in your /src/main/webapp/WEB-INF/web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4">
  ...
  <servlet>
    <servlet-name>versionServlet</servlet-name>
    <servlet-class>com.randomnoun.common.servlet.VersionServlet</servlet-class>
  </servlet>
  ...
  <servlet-mapping>
    <servlet-name>versionServlet</servlet-name>
    <url-pattern>/version</url-pattern>
  </servlet-mapping>
  ...
</web-app>

So everyone start doing that, and then you’ll be able to more easily turn back time, which is what you’ll need to be able to do once Sam in accounts discovers how to modify his own payroll or something.

This class is now part in the com.randomnoun.common:common-public artifact, which can be directly referenced in your pom.xml.

Update 2013-09-25: It’s in central now
Update 2021-01-09: It’s on github now

[1] a.k.a. DreamChess 0.2.0
[2] Also, hopefully, how easy it is to get people to run programs they’ve downloaded off the internet, thereby negating the efforts of the entire software security industry. Bonus points: stop your computer from thinking about elephants.
[3] I’m assuming for the moment here that you have a problem with the software that you’re using / writing.
Tags:,

Add a Comment

Your email address will not be published. Required fields are marked *