Skip to content

Commit

Permalink
Add /api/history support
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Feb 14, 2011
1 parent 2c837e0 commit ba794aa
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 1 deletion.
1 change: 1 addition & 0 deletions rundeckapp/grails-app/conf/UrlMappings.groovy
Expand Up @@ -26,6 +26,7 @@ class UrlMappings {

"/api/projects" (controller: 'framework', action: 'listProjects')
"/api/project/$project?" (controller: 'framework', action: 'getProject')
"/api/history" (controller: 'reports', action: 'apiHistory')
"/api/renderError" (controller: 'api', action: 'renderError')
"/api/error" (controller: 'api', action: 'error')
"/api/$action?" (controller: 'api', action: 'invalid')
Expand Down
106 changes: 106 additions & 0 deletions rundeckapp/grails-app/controllers/ReportsController.groovy
@@ -1,6 +1,9 @@
import org.springframework.web.servlet.ModelAndView
import com.dtolabs.client.utils.Constants
import com.dtolabs.rundeck.core.common.Framework
import java.util.regex.Matcher
import java.text.SimpleDateFormat
import java.text.ParseException

class ReportsController {
def reportService
Expand Down Expand Up @@ -420,4 +423,107 @@ class ReportsController {
}

}

/**
* API actions
*
*/

/**
* Utility: parse string into Date, as either unix millisecond, or W3C date format.
*/
public static Date parseDate(String input){
long endtime=-1
try{
endtime=Long.parseLong(input)
}catch(Exception e){

}
if(endtime>0){
return new Date(endtime)
}
//attempt to parse w3c dateTime format:
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.parse(input)
}


/**
* API, /api/history, version 1.2
*/
def apiHistory={ReportQuery query->
if(!params.project){
flash.error=g.message(code:'api.error.parameter.required',args:['project'])
return chain(controller:'api',action:'error')
}
//test valid project
Framework framework = frameworkService.getFrameworkFromUserSession(session,request)

def exists=frameworkService.existsFrameworkProject(params.project,framework)
if(!exists){
flash.error=g.message(code:'api.error.item.doesnotexist',args:['project',params.project])
return chain(controller:'api',action:'error')
}

params.projFilter=params.project
query.projFilter = params.project


//attempt to parse/bind "end" and "begin" parameters
if(params.begin){
try{
query.endafterFilter=parseDate(params.begin)
query.doendafterFilter=true
}catch(ParseException e){
flash.error=g.message(code:'api.error.history.date-format',args:['begin',params.begin])
return chain(controller:'api',action:'error')
}
}
if(params.end){
try{
query.endbeforeFilter=parseDate(params.end)
query.doendbeforeFilter=true
}catch(ParseException e){
flash.error=g.message(code:'api.error.history.date-format',args:['end',params.end])
return chain(controller:'api',action:'error')
}
}

if(null!=query){
query.configureFilter()
}
def model=reportService.getCombinedReports(query)
model = reportService.finishquery(query,params,model)

return new ApiController().success{ delegate->
delegate.'events'(count:model.reports.size(),total:model.total, max: model.max, offset: model.offset){
model.reports.each{ rpt->
def nodes=rpt.node
final Matcher matcher = nodes =~ /^(\d+)\/(\d+)\/(\d+)$/
def nodesum=[rpt.status =='succeed'?1:0,rpt.status =='succeed'?0:1,1]
if(matcher.matches()){
nodesum[0]=matcher.group(1)
nodesum[1]=matcher.group(2)
nodesum[2]=matcher.group(3)
}
event(starttime:rpt.dateStarted.time,endtime:rpt.dateCompleted.time){
title(rpt.reportId?:'adhoc')
summary(rpt.adhocScript?:rpt.title)
delegate.'node-summary'(succeeded:nodesum[0],failed:nodesum[1],total:nodesum[2])
user(rpt.author)
project(rpt.ctxProject)
delegate.'date-started'(g.w3cDateValue(date:rpt.dateStarted))
delegate.'date-ended'(g.w3cDateValue(date:rpt.dateCompleted))
if(rpt.jcJobId){
job(id:rpt.jcJobId)
}
if(rpt.jcExecId){
execution(id:rpt.jcExecId)
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions rundeckapp/grails-app/i18n/messages.properties
Expand Up @@ -256,4 +256,5 @@ api.error.jobs.import.missing-file=No file was uploaded
api.error.jobs.import.format.unsupported=The specified format is not supported: {0}
api.error.jobs.import.empty=Jobs Document did not define any jobs, or was invalid
api.error.run-script.upload.is-empty=Input script file was empty
api.error.history.date-format=The parameter "{0}" did not have a valid time or dateTime format: {1}
api.success.job.delete.message=Job was successfully deleted: {0}
6 changes: 5 additions & 1 deletion rundeckapp/grails-app/services/ReportService.groovy
Expand Up @@ -304,7 +304,11 @@ class ReportService implements ReportAcceptor {
}

eqfilters.each {key, val ->
if (query["${key}Filter"]) {
if (query["${key}Filter"] == 'null') {
isNull(val)
} else if (query["${key}Filter"] == '!null') {
isNotNull(val)
} else if (query["${key}Filter"]) {
eq(val, query["${key}Filter"])
}
}
Expand Down
99 changes: 99 additions & 0 deletions test/api/test-history.sh
@@ -0,0 +1,99 @@
#!/bin/bash

#Test api: /api/history output

errorMsg() {
echo "$*" 1>&2
}

DIR=$(cd `dirname $0` && pwd)

# accept url argument on commandline, if '-' use default
url="$1"
if [ "-" == "$1" ] ; then
url='http://localhost:4440'
fi
shift

proj="test"

apiurl="${url}/api"
VERSHEADER="X-RUNDECK-API-VERSION: 1.2"

# curl opts to use a cookie jar, and follow redirects, showing only errors
CURLOPTS="-s -S -L -c $DIR/cookies -b $DIR/cookies"
CURL="curl $CURLOPTS"

XMLSTARLET=xml

# now submit req
runurl="${apiurl}/history"

echo "TEST: output from /api/history should be valid"

params="project=${proj}"

# get listing
$CURL --header "$VERSHEADER" ${runurl}?${params} > $DIR/curl.out
if [ 0 != $? ] ; then
errorMsg "ERROR: failed query request"
exit 2
fi

sh $DIR/api-test-success.sh $DIR/curl.out || exit 2

#Check projects list
itemcount=$($XMLSTARLET sel -T -t -v "/result/events/@count" $DIR/curl.out)
if [ "" == "$itemcount" ] ; then
errorMsg "FAIL: expected events count"
exit 2
fi

echo "OK"

# use invalid dateTime format for "end" parameter

echo "TEST: /api/history using bad \"end\" date format parameter"
params="project=${proj}&end=asdf"

sh $DIR/api-expect-error.sh "${runurl}" "${params}" "The parameter \"end\" did not have a valid time or dateTime format: asdf" || exit 2
echo "OK"


# use invalid dateTime format for "begin" parameter

echo "TEST: /api/history using bad \"begin\" date format parameter"
params="project=${proj}&begin=asdf"

sh $DIR/api-expect-error.sh "${runurl}" "${params}" "The parameter \"begin\" did not have a valid time or dateTime format: asdf" || exit 2
echo "OK"

# use valid dateTime format for "end" parameter

echo "TEST: /api/history using valid \"end\" date format parameter"
params="project=${proj}&end=2011-02-04T21:38:02Z"

$CURL --header "$VERSHEADER" ${runurl}?${params} > $DIR/curl.out
if [ 0 != $? ] ; then
errorMsg "ERROR: failed query request"
exit 2
fi

sh $DIR/api-test-success.sh $DIR/curl.out || exit 2
echo "OK"
# use valid dateTime format for "begin" parameter

echo "TEST: /api/history using valid \"begin\" date format parameter"
params="project=${proj}&begin=2011-02-04T21:03:34Z"

$CURL --header "$VERSHEADER" ${runurl}?${params} > $DIR/curl.out
if [ 0 != $? ] ; then
errorMsg "ERROR: failed query request"
exit 2
fi

sh $DIR/api-test-success.sh $DIR/curl.out || exit 2
echo "OK"

#rm $DIR/curl.out

93 changes: 93 additions & 0 deletions test/api/util-history.sh
@@ -0,0 +1,93 @@
#!/bin/bash

#Usage:
# util-history.sh <project> [URL] [param=value [param=value] .. ]

errorMsg() {
echo "$*" 1>&2
}

DIR=$(cd `dirname $0` && pwd)

proj=$1
if [ "" == "$1" ] ; then
proj="test"
fi
shift
# accept url argument on commandline, if '-' use default
url="$1"
if [ "-" == "$1" ] ; then
url='http://localhost:4440'
fi
shift
apiurl="${url}/api"
VERSHEADER="X-RUNDECK-API-VERSION: 1.2"

# curl opts to use a cookie jar, and follow redirects, showing only errors
CURLOPTS="-s -S -L -c $DIR/cookies -b $DIR/cookies"
CURL="curl $CURLOPTS"

if [ ! -f $DIR/cookies ] ; then
# call rundecklogin.sh
sh $DIR/rundecklogin.sh $url
fi

XMLSTARLET=xml

# now submit req
runurl="${apiurl}/history"

echo "# Listing RunDeck Jobs for project ${proj}..."

args="$*"
params="project=${proj}&${args}"


# get listing
$CURL --header "$VERSHEADER" ${runurl}?${params} > $DIR/curl.out
if [ 0 != $? ] ; then
errorMsg "ERROR: failed query request"
exit 2
fi

#test curl.out for valid xml
$XMLSTARLET val -w $DIR/curl.out > /dev/null 2>&1
if [ 0 != $? ] ; then
errorMsg "ERROR: Response was not valid xml"
exit 2
fi

#test for expected /joblist element
$XMLSTARLET el $DIR/curl.out | grep -e '^result' -q
if [ 0 != $? ] ; then
errorMsg "ERROR: Response did not contain expected result"
exit 2
fi

# job list query doesn't wrap result in common result wrapper
#If <result error="true"> then an error occured.
waserror=$($XMLSTARLET sel -T -t -v "/result/@error" $DIR/curl.out)
if [ "true" == "$waserror" ] ; then
errorMsg "Server reported an error: "
$XMLSTARLET sel -T -t -v "/result/error/message" -n $DIR/curl.out
exit 2
fi

#Check projects list
itemcount=$($XMLSTARLET sel -T -t -v "/result/events/@count" $DIR/curl.out)

if [ "0" != "$itemcount" ] ; then
#echo all on one line
$XMLSTARLET sel -T -t -m "/result/events/event" -o "[" \
-v "date-ended" -o "] " \
-v "user" -o " : " -v "project" -o " [" \
-v "job/@id" -o "," -v "execution/@id" -o "] [" \
-v "node-summary/@succeeded" -o "/" \
-v "node-summary/@failed" -o "/" \
-v "node-summary/@total" -o "] " \
-v "title" -o " : " \
-v "normalize-space(summary)" \
-n $DIR/curl.out
fi

rm $DIR/curl.out

0 comments on commit ba794aa

Please sign in to comment.