How to store and retrieve build result metadata

You can use the build result to collect build metadata during the build and store it in the DBB metadata database. The build metadata includes the following information:

  • The current build state: NEW, PROCESSING, COMPLETE, ABANDONED
  • The current built status: CLEAN, ERROR, WARNING
  • The build report in HTML, JSON, or both formats
  • Additional file attachments such as log files
  • Simple information in the form of name-value pair properties

To create a build result, you must provide values for group and label.

  • group: Build group ID for a set of stored build results
  • label: A unique name for the specific build result

You can use the following methods that are provided by the DBB MetadataStore class to create, retrieve, and delete build results:

  • createBuildResult(group, label)
  • getBuildResult(group, label)
  • deleteBuildResult(buildResult)

Complete the following procedure to work with build result metadata:

  1. Creating a metadata store connection
  2. Creating a new build result
  3. Accessing build result metadata
  4. Searching for build results

Creating a metadata store connection

As build results are database artifacts, an active metadata store connection is required to create and modify them. For more information about initializing the MetadataStore utility class, see Metadata store.

Creating a new build result

The DBB toolkit provides a BuildResult interface for accessing and modifying a build result. It is located in the com.ibm.dbb.metadata package.

To create a new build result, you must provide a group ID and label. The group ID refers to the build project name and must be unique across all projects. The label is the ID of the specific build being executed and must be unique within the scope of the group ID. Use the MetadataStore to create a new build result.

def metadataStore = MetadataStoreFactory.createDb2MetadataStore(id, passwordFile, db2ConnectionProps)
def group = "module_1"
def label = "build-${new Date()}"
def buildResult = metadataStore.createBuildResult(group, label)

When this method is completed, a new build result is created and stored in the DBB metadata database. The buildResult instance is synchronized to the state of the build result that is stored in the database. As changes are made to the build result instance, they are automatically updated in the metadata database.

Accessing build result metadata

As mentioned earlier, the BuildResult interface can be used to add or retrieve several different types of data to the build result that is stored in the metadata database. Below is a description the build report data types and how to access them.

Database fields

When a build result is created in the DBB metadata database, several fields are automatically created and maintained by the metadata store. These fields are considered read-only and are only accessible through getter methods.

  • id : getId() - The unique ID (long) assigned to the database artifact by the metadata store
  • group : getGroup() - The group ID that the user provided when the build result was created
  • label : getLabel() - Label (ID of the specific build) that the user provided when the build result was created
  • created : getCreated() - Date when the build result was created
  • createdBy : getCreatedBy() - The user ID of the person who created the build result
  • lastUpdated : getLastUpdated() - Date when the build result was last modified
  • lastUpdatedBy : getLastUpdatedBy() - The user ID of the person who last modified the build result

Build report file attachments

As outlined in How to create and access a DBB build report, DBB provides APIs to generate and save a summary build report to the local file system at the end of the build process. The build report itself consists of two files: a JSON data file and a rendering HTML file. These files can be attached to a build result for later retrieval or viewing.

// After the build report files have been saved/generated to the local file system
def htmlOutputFile = new File("usr1/build/BuildReport.html")
def jsonOutputFile = new File("usr1/build/BuildReport.json")
buildResult.setBuildReport(new FileInputStream(htmlOutputFile))
buildResult.setBuildReportData(new FileInputStream(jsonOutputFile))

// Download the build report file content and write to a local file
def buildResult = metadataStore.getBuildResult(group, label)
def buildReportStream = buildResult.getBuildReport()
def buildReportDataStream = buildResult.getBuildReportData()
Files.copy(buildReportStream, new Path("usr2/buildReport.html"))
Files.copy(buildReportDataStream, new Path("usr2/buildReport.json"))

// Delete build report from build result by setting the fields to null
buildResult.setBuildReport(null)
buildResult.setBuildReportData(null)

Build state and status

Build state refers to the current state of the build, that is, whether it is running or it has completed. Build status refers to whether the build was successful and everything was compiled cleanly or failed for some reason. Both build state and status are stored as int fields in the build result. You can set whatever values you choose and assign meaning to those values. You can also use the pre-defined constants of the BuildResult interface.

// Constants for build state
static final int NEW = 0;
static final int PROCESSING = 1;
static final int COMPLETE = 2;
static final int ABANDONED = 3;
  
// Constants for build status
static final int CLEAN = 0;
static final int ERROR = 1;
static final int WARNING = 2;

When a build result is created, both state and status values are set to 0. As the build process progresses, it is your responsibility to update the state and status to the current appropriate value.

// When the build starts set the state to processing
buildResult.setState(BuildResult.PROCESSING)

// Set the state to a user defined state
int STAGE_5 = 5
buildResult.setState(STAGE_5)

// Mark the build as failed if a program compiles with errors but let the process continue
def rc = compile.execute()
if (rc > 8) {
   buildResult.setStatus(BuildResult.ERROR)
}

// Use the build status to store the highest RC returned during the build process
def rc = compile.execute()
if (rc > buildResult.getStatus()) { 
   buildResult.setStatus(rc)
}

// At the end of the build process update the build state to completed
buildResult.setState(BuildResult.COMPLETE)

User-defined properties

You can also define your own properties and store them in the build result. Build result properties are similar to normal key value properties with the exception that a property key can have more than one value. If a key does have more than one value, each value is unique. The internal storage representation for user-defined properties is Map<String, Set<String>>.

Because a key can have more than one value, build result properties have additional accessor methods.

Accessor Method Description
getProperty(key) Returns a single String value for the property or null if the property does not exist. If the property has more than one value, it returns the first value in the value Set (order undefined).
getPropertyValues(key) Always returns a String Set that contains the values of the property. If the property does not exist, an empty String Set is returned.
getPropertyNames() Always returns a String Set that contains all the property keys in the build result. If no properties exist in the build result, an empty String Set is returned.
addProperty(key, value) Adds a property value to an existing property. If the property does not exist, one is created automatically and the value is added. If the property already contains the value being added, the action is ignored.
addProperty(key, values) Adds a Set of values to an existing property. If the property does not exist, one is created automatically and the value is added. If the property already contains values being added from the Set, those values are ignored.
setProperty(key, value) Creates a new property and value. If the property already exists, it is first deleted and the new property and value are added.
setProperty(key, values) Creates a new property and value Set. If the property already exists, it is first deleted and the new property and value Set are added.
deleteProperty(key) Deletes the property and all of its values. If the property does not exist, the action is ignored.
deleteProperty(key, value) Deletes a specific value of a property but leaves the property and the rest of its values as is. If the property value does not exist, the action is ignored.

Example:

// Set a property with the current Git commit hash for the build
def process = "git rev-parse HEAD".execute()
def hash = process.getOut().readLines()
buildResult.setProperty("buildHash", hash)

// Add an error message when a file fails to compile
def rc = compile.execute()
def maxRC = 8
if (rc > maxRC) {
   buildResult.setStatus(BuildResult.ERROR)
   buildResult.addProperty("error", "*! The return code ($rc) for $file exceeded the maximum return code allowed ($maxRC)" 
}

// Iterate through all of the build result properties and print them to the console
def keys = buildResult.getPropertyNames()
keys.each { key ->
   def values = buildResult.getPropertyValues(key)
   values.each { value ->
      println("$key = $value")
   }
}   

File attachments

In addition to the build report file attachments mentioned earlier, you can also save other file attachments. APIs are provided to add, retrieve, update, and delete attachments.

// Attach a SYSPRINT log file to the build result"
buildResult.addAttachment("${member}.log", new FileInputStream("/usr1/build/${member}.log"))

// Attach an XML file to the build result adding optional content type value of xml.
buildResult.addAttachment("xml_data", "xml", new FileInputStream("/usr1/build/data.xml"))

// Get an attachment and print it to the console
def content = buildResult.getAttachment("sysprint.log")
content.eachLine { line ->
    println(line)
}

// Delete all attachments
def attachments = buildResult.getAttachments()
attachments.each { attachment ->
    buildResult.deleteAttachment(attachment.getName())
}

Searching for build results

An earlier example showed how to retrieve a build result by using the MetadataStore getBuildResult(group, label) method. This method retrieves a specific build result and requires that you know both the group ID and label of the build result to be retrieved. DBB also provides APIs to help iterate through build results and includes some limited query functionality.

Iterating build results

The MetadataStore has two methods for build result iteration: listBuildResultGroups() and listBuildResultLabels(group).

// Iterate through all On-line build results tallying the number of build failures
def numFail = 0
def groups = metadataStore.listBuildResultGroups()
groups.each { group ->
    if (group.startsWith("ON-LINE")) {
        def labels = metadataStore.listBuildResultLabels(group)
        label.each { label ->
            def buildResult = metadataStore.getBuildResult(group, label)
            if (buildResult.getStatus == BuildResult.ERROR)
                numFail++
        }
    }
} 

Querying for build results

The MetadataStore also provides two methods for querying build results: getBuildResults(queryParms) and getLastBuildResult(group, state, status).

The getBuildResults method returns all the build results that apply to the query parameters provided. If no query parameters are provided, then all the build results in the metadata database are returned. The following query parameters are supported:

  • group: Limits list to build results with the same group.
  • label: Limits list to build results with the same label.
  • property: Limits list to build results with an associated property name.
  • value: Limits list to build results with an associated property value.
  • state: Limits list to build results with a state equal to supplied value. NEW, PROCESSING, COMPLETE, and ABANDONED are also acceptable values.
  • status: Limits list to build results with a status equal to supplied value. CLEAN, ERROR, and WARNING are also acceptable values.
  • orderBy: Returns the list sorted on the specified field (id, group, label, created, createdBy, lastUpdated, lastUpdatedBy). Other values are ignored.
  • order: Sorting order is ascending(ASC) or descending(DESC). Order defaults to ASC.
// Get a list of all the release dates for ON-LINE-APPS
def queryParms = ["group":"ON-LINE-APPS","property":"RELEASE-DATE"]
def buildResults = metadataStore.getBuildResults(queryParms)
def releaseDates = []
buildResults.each { buildResult ->
   releaseDates << buildResult.getProperty("RELEASE-DATE")
}

// Print the error messages for the last five failed builds for BATCH-PROCESSES
def queryParms = ["group":"BATCH-PROCESSES","status":"1","orderBy":"lastUpdated","order":"DESC"]
def buildResults = metadataStore.getBuildResults(queryParms)
for (i = 0; i < 5; i++) {
   def buildResult = buildResults[i]
   def errMsgs =  buildResult.getPropertyValues("ERR-MSG")
   errMsgs.each { errMsg ->
      println("${buildResult.getLabel()} : ERR-MSG=$errMsg")
   }
}

// Get the last successful build for BATCH-PROCESSES
def queryParms = ["group":"BATCH-PROCESSES","status":"0","state":"2",orderBy":"lastUpdated","order":"DESC"]
def buildResults = metadataStore.getBuildResults(queryParms)
def buildResult = buildResults[0]

The MetadataStore also provides the getLastBuildResult(group, state, status) method to make the last example shown a little easier:

def buildResult = metadataStore.getLastBuildResult("BATCH-PROCESSES", BuildResult.COMPLETE, BuildResult.CLEAN)