diff --git a/CHANGELOG.md b/CHANGELOG.md index bdc7ff1fe..de1d0c4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 1.11.1 - 2020-09-29 + +### Added +- Added healtz endpoint that is cheap and quick to return, useful for kubernetes live/ready checks. + +### Fixed +- Fixed health check script when using custom path prefix. +- Proxy will no correctly handle paths that end with a / at the end. +- Submitting an extraction will always return a 500 error, see [#84](https://github.com/clowder-framework/clowder/issues/84) +- Added MongoDB index for `folders.files`. + +### Changed +- Updated update-clowder script to work with migration to github. Has the ability now to push a message to MSTEAMS as well as influxdb. + ## 1.11.0 - 2020-08-31 ### Added diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1b5c1e124..ae8fd60b7 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,5 +1,6 @@ Following is a list of contributors in alphabetical order: +- Aaraj Habib - Ashwini Vaidya - Avinash Kumar - Ben Galewsky diff --git a/app/api/Extractions.scala b/app/api/Extractions.scala index c1759b68d..021b20a2e 100644 --- a/app/api/Extractions.scala +++ b/app/api/Extractions.scala @@ -530,22 +530,24 @@ class Extractions @Inject()( datasetId = datasetslists.head.id } // if extractor_id is not specified default to execution of all extractors matching mime type - val key = (request.body \ "extractor").asOpt[String] match { + (request.body \ "extractor").asOpt[String] match { case Some(extractorId) => val job_id = p.submitFileManually(new UUID(originalId), file, Utils.baseUrl(request), extractorId, extra, datasetId, newFlags, request.apiKey, request.user) Ok(Json.obj("status" -> "OK", "job_id" -> job_id)) - case None => + case None => { p.fileCreated(file, None, Utils.baseUrl(request), request.apiKey) match { case Some(job_id) => { Ok(Json.obj("status" -> "OK", "job_id" -> job_id)) } + case None => { + val message = "No jobId found for Extraction on fileid=" + file_id.stringify + Logger.error(message) + InternalServerError(toJson(Map("status" -> "KO", "msg" -> message))) + } } + } } - - val message = "No jobId found for Extraction on fileid=" + file_id.stringify - Logger.error(message) - InternalServerError(toJson(Map("status" -> "KO", "msg" -> message))) } else { Conflict(toJson(Map("status" -> "error", "msg" -> "File is not ready. Please wait and try again."))) } diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index 07bc5ddd0..fa70ce3fb 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -31,8 +31,8 @@ class Application @Inject() (files: FileService, collections: CollectionService, * @param path the path minus the slash * @return moved permanently to path without / */ - def untrail(path: String) = Action { - MovedPermanently("/" + path) + def untrail(path: String) = Action { implicit request => + MovedPermanently(s"${Utils.baseUrl(request, false)}/${path}") } def swaggerUI = Action { implicit request => @@ -263,6 +263,10 @@ class Application @Inject() (files: FileService, collections: CollectionService, Ok("") } + def healthz() = Action { implicit request => + Ok("healthy") + } + /** * Bookmarklet */ diff --git a/app/controllers/Utils.scala b/app/controllers/Utils.scala index f759e97d0..8eda878fb 100644 --- a/app/controllers/Utils.scala +++ b/app/controllers/Utils.scala @@ -14,8 +14,12 @@ object Utils { * Return base url given a request. This will add http or https to the front, for example * https://localhost:9443 will be returned if it is using https. */ - def baseUrl(request: Request[Any]) = { - routes.Files.list().absoluteURL(https(request))(request).replace("/files", "") + def baseUrl(request: Request[Any], absolute: Boolean = true) = { + if (absolute) { + routes.Files.list().absoluteURL(https(request))(request).replace("/files", "") + } else { + routes.Files.list().url.replace("/files", "") + } } /** diff --git a/app/services/mongodb/MongoSalatPlugin.scala b/app/services/mongodb/MongoSalatPlugin.scala index 5705bf6d7..2389e400b 100644 --- a/app/services/mongodb/MongoSalatPlugin.scala +++ b/app/services/mongodb/MongoSalatPlugin.scala @@ -141,6 +141,7 @@ class MongoSalatPlugin(app: Application) extends Plugin { collection("extractions").ensureIndex(MongoDBObject("file_id" -> 1)) collection("folders").ensureIndex(MongoDBObject("parentDatasetId" -> 1)) + collection("folders").ensureIndex(MongoDBObject("files" -> 1)) collection("uploads").ensureIndex(MongoDBObject("uploadDate" -> -1)) collection("uploads").ensureIndex(MongoDBObject("author.email" -> 1)) diff --git a/conf/routes b/conf/routes index 64530b9e2..b8d703c87 100644 --- a/conf/routes +++ b/conf/routes @@ -7,6 +7,13 @@ #OPTIONS /*path @controllers.Application.options(path) OPTIONS /*path api.ApiHelp.options(path) + +# operations are applied in this order, to prevent untrail from removing +# the trailing / at the end of the /api/proxy calls it is placed before +# the untrail call +GET /api/proxy/:endpoint_key @api.Proxy.get(endpoint_key: String, pathSuffix: String = null) +GET /api/proxy/:endpoint_key/ @api.Proxy.get(endpoint_key: String, pathSuffix: String = "") +GET /api/proxy/:endpoint_key/*pathSuffix @api.Proxy.get(endpoint_key: String, pathSuffix: String) GET /*path/ @controllers.Application.untrail(path: String) # ---------------------------------------------------------------------- @@ -17,6 +24,7 @@ GET / GET /about @controllers.Application.about GET /tos @controllers.Application.tos(redirect: Option[String] ?= None) GET /email @controllers.Application.email(subject: String ?= "", body: String ?= "") +GET /healthz @controllers.Application.healthz() # ---------------------------------------------------------------------- # Map static resources from the /public folder to the /assets URL path @@ -859,17 +867,14 @@ DELETE /api/standardvocab/:id # PROXY API # ---------------------------------------------------------------------- -GET /api/proxy/:endpoint_key @api.Proxy.get(endpoint_key: String, pathSuffix: String = null) -GET /api/proxy/:endpoint_key/ @api.Proxy.get(endpoint_key: String, pathSuffix: String = null) -GET /api/proxy/:endpoint_key/*pathSuffix @api.Proxy.get(endpoint_key: String, pathSuffix: String) POST /api/proxy/:endpoint_key @api.Proxy.post(endpoint_key: String, pathSuffix: String = null) -POST /api/proxy/:endpoint_key/ @api.Proxy.post(endpoint_key: String, pathSuffix: String = null) +POST /api/proxy/:endpoint_key/ @api.Proxy.post(endpoint_key: String, pathSuffix: String = "") POST /api/proxy/:endpoint_key/*pathSuffix @api.Proxy.post(endpoint_key: String, pathSuffix: String) PUT /api/proxy/:endpoint_key @api.Proxy.put(endpoint_key: String, pathSuffix: String = null) -PUT /api/proxy/:endpoint_key/ @api.Proxy.put(endpoint_key: String, pathSuffix: String = null) +PUT /api/proxy/:endpoint_key/ @api.Proxy.put(endpoint_key: String, pathSuffix: String = "") PUT /api/proxy/:endpoint_key/*pathSuffix @api.Proxy.put(endpoint_key: String, pathSuffix: String) DELETE /api/proxy/:endpoint_key @api.Proxy.delete(endpoint_key: String, pathSuffix: String = null) -DELETE /api/proxy/:endpoint_key/ @api.Proxy.delete(endpoint_key: String, pathSuffix: String = null) +DELETE /api/proxy/:endpoint_key/ @api.Proxy.delete(endpoint_key: String, pathSuffix: String = "") DELETE /api/proxy/:endpoint_key/*pathSuffix @api.Proxy.delete(endpoint_key: String, pathSuffix: String) # ---------------------------------------------------------------------- diff --git a/doc/src/sphinx/conf.py b/doc/src/sphinx/conf.py index 6a2bc7e79..bff310b81 100644 --- a/doc/src/sphinx/conf.py +++ b/doc/src/sphinx/conf.py @@ -22,7 +22,7 @@ author = 'Luigi Marini' # The full version, including alpha/beta/rc tags -release = '1.11.0' +release = '1.11.1' # -- General configuration --------------------------------------------------- diff --git a/docker/healthcheck.sh b/docker/healthcheck.sh index c5a7564b6..80e64d498 100755 --- a/docker/healthcheck.sh +++ b/docker/healthcheck.sh @@ -1,3 +1,6 @@ #!/bin/bash -curl -s --fail http://localhost:9000/api/status || exit 1 +### Add trailing backslash if not in context +[[ "${CLOWDER_CONTEXT}" != */ ]] && CLOWDER_CONTEXT="${CLOWDER_CONTEXT}/" + +curl -s --fail http://localhost:8000${CLOWDER_CONTEXT:-/}api/status || exit 1 diff --git a/project/Build.scala b/project/Build.scala index 2f45e4312..c17a04d60 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -13,7 +13,7 @@ import NativePackagerKeys._ object ApplicationBuild extends Build { val appName = "clowder" - val version = "1.11.0" + val version = "1.11.1" val jvm = "1.7" def appVersion: String = { diff --git a/public/jsonld/contexts/extractors.jsonld b/public/jsonld/contexts/extractors.jsonld index 7ad8f6335..4250cfd60 100644 --- a/public/jsonld/contexts/extractors.jsonld +++ b/public/jsonld/contexts/extractors.jsonld @@ -3,7 +3,7 @@ "extractor": "https://clowder.ncsa.illinois.edu/contexts/extractor#", "id":"extractor/internal_id", "name": {"@id": "extractor:extractor/id", "@type": "@id"}, - "version": "extractor:version" + "version": "extractor:version", "created_at": { "@id": "extractor:updated", "@type": "http://www.w3.org/2001/XMLSchema#dateTime" @@ -19,4 +19,4 @@ "libraries": "extractor:libraries", "bibtex": "extractor:bibtex", "dependencies":"extractor:dependencies" -} \ No newline at end of file +} diff --git a/public/jsonld/contexts/metadata.jsonld b/public/jsonld/contexts/metadata.jsonld index 802ef30ea..573cfb40f 100644 --- a/public/jsonld/contexts/metadata.jsonld +++ b/public/jsonld/contexts/metadata.jsonld @@ -20,11 +20,9 @@ "extractor": "cat:extractor", "content": { "@id": "https://clowder.ncsa.illinois.edu/metadata#content" - } + }, "attached_to": "https://clowder.ncsa.illinois.edu/metadata/attachedTo", "resource_type": "https://clowder.ncsa.illinois.edu/resource/type", "url": "https://clowder.ncsa.illinois.edu/resource" } } - - diff --git a/public/swagger.yml b/public/swagger.yml index c31a641ce..7fe11a73b 100644 --- a/public/swagger.yml +++ b/public/swagger.yml @@ -9,7 +9,7 @@ info: Clowder is a customizable and scalable data management system to support any data format and multiple research domains. It is under active development and deployed for a variety of research projects. - version: 1.11.0 + version: 1.11.1 termsOfService: https://clowder.ncsa.illinois.edu/clowder/tos contact: name: Clowder diff --git a/scripts/ubuntu/update-clowder.sh b/scripts/ubuntu/update-clowder.sh index e7a2f7312..c96a778d5 100644 --- a/scripts/ubuntu/update-clowder.sh +++ b/scripts/ubuntu/update-clowder.sh @@ -1,48 +1,83 @@ #!/bin/bash -# CATS-CORE is the main branch for this server -CLOWDER_BRANCH=${CLOWDER_BRANCH:-"CATS-CORE"} -CLOWDER_BUILD=${CLOWDER_BUILD:-"latestSuccessful"} +# what version ("" == latest release) +CLOWDER_BRANCH=${CLOWDER_BRANCH:-} +#CLOWDER_BRANCH="develop" + +# compute some other variables +if [ "${CLOWDER_BRANCH}" = "" ]; then + CLOWDER_VERSION=$(curl -s https://opensource.ncsa.illinois.edu/projects/artifacts.php?key=CATS | grep '#\1#') + CLOWDER_ZIPFILE=clowder-${CLOWDER_VERSION}.zip +else + CLOWDER_VERSION="${CLOWDER_BRANCH}" + CLOWDER_ZIPFILE=clowder-${CLOWDER_VERSION}-${CLOWDER_BRANCH}.zip +fi + +# Should text go to stdout, empty is not +STDOUT="YES" + +# HipChat token for notifications +HIPCHAT_TOKEN="" +HIPCHAT_ROOM="" + +# Slack token for notifications +SLACK_TOKEN="" +SLACK_CHANNEL="#simpl-ops" + +# MSTeams Webhook +MSTEAMS_URL="" + +# INFLUXDB +INFLUXDB_URL="" +INFLUXDB_DATABASE="" +INFLUXDB_USERNAME="" +INFLUXDB_PASSWORD="" # change to folder where script is installed cd /home/clowder # fetch software -if [[ ${CLOWDER_BUILD} == latest* ]]; then - BB="${CLOWDER_BRANCH}/${CLOWDER_BUILD}" +if [ -e ]; then + curl -f -s -z "${CLOWDER_ZIPFILE}" -o "${CLOWDER_ZIPFILE}" https://opensource.ncsa.illinois.edu/projects/artifacts/CATS/${CLOWDER_VERSION}/files/${CLOWDER_ZIPFILE} || exit 1 else - BB="${CLOWDER_BRANCH}-${CLOWDER_BUILD}" + curl -f -s -o "${CLOWDER_ZIPFILE}" https://opensource.ncsa.illinois.edu/projects/artifacts/CATS/${CLOWDER_VERSION}/files/${CLOWDER_ZIPFILE} || exit 1 fi -URL="https://opensource.ncsa.illinois.edu/bamboo/browse/${BB}/artifact/shared/dist/" -/usr/bin/wget -q -e robots=off -A "clowder-*.zip" -nd -r -N -l1 ${URL} -LATEST=$( /bin/ls -1rt clowder-*.zip | tail -1 ) -if [ -s ${LATEST} ]; then - if [ "$1" == "--force" -o ${LATEST} -nt clowder ]; then +if [ -s ${CLOWDER_ZIPFILE} ]; then + if [ "$1" == "--force" -o ${CLOWDER_ZIPFILE} -nt clowder ]; then + exec 3>&1 + exec &> "/tmp/$$.txt" + echo "UPDATING CLOWDER ON ${HOSTNAME}" echo " bamboo branch = ${CLOWDER_BRANCH}" # stop clowder - /sbin/stop clowder + /bin/systemctl stop clowder # save local modifications if [ -d clowder/custom ]; then mv clowder/custom clowder.custom fi + if [ -d clowder/logs ]; then + mv clowder/logs clowder.logs + fi # install new version - /bin/rm -rf clowder $( basename ${LATEST} .zip ) - /usr/bin/unzip -q ${LATEST} - /bin/mv $( basename ${LATEST} .zip ) clowder + /bin/rm -rf clowder $( basename ${CLOWDER_ZIPFILE} .zip ) + /usr/bin/unzip -q ${CLOWDER_ZIPFILE} + /bin/mv $( basename ${CLOWDER_ZIPFILE} .zip ) clowder /usr/bin/touch clowder # get some nice values from the build - echo " bamboo build = $( grep '\-Dbuild.bamboo' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )" + echo " github build = $( grep '\-Dbuild.bamboo' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )" echo " clowder version = $( grep '\-Dbuild.version' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )" echo " clowder branch = $( grep '\-Dbuild.branch' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )" echo " clowder gitsha1 = $( grep '\-Dbuild.gitsha1' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )" # restore local modifications + if [ -d clowder.logs ]; then + mv clowder.logs clowder/logs + fi if [ -d clowder.custom ]; then mv clowder.custom clowder/custom fi @@ -56,6 +91,41 @@ if [ -s ${LATEST} ]; then fi # start clowder again - /sbin/start clowder + /bin/systemctl start clowder + + # send message by hipchat + if [ "${HIPCHAT_TOKEN}" != "" -a "${HIPCHAT_ROOM}" != "" ]; then + url="https://hipchat.ncsa.illinois.edu/v1/rooms/message?format=json&auth_token=${HIPCHAT_TOKEN}" + txt=$(cat /tmp/$$.txt | sed 's/ /%20/g;s/!/%21/g;s/"/%22/g;s/#/%23/g;s/\$/%24/g;s/\&/%26/g;s/'\''/%27/g;s/(/%28/g;s/)/%29/g;s/:/%3A/g;s/$//g') + room=$(echo ${HIPCHAT_ROOM} | sed 's/ /%20/g;s/!/%21/g;s/"/%22/g;s/#/%23/g;s/\$/%24/g;s/\&/%26/g;s/'\''/%27/g;s/(/%28/g;s/)/%29/g;s/:/%3A/g') + body="room_id=${room}&from=clowder&message=${txt}" + result=$(curl -s -X POST -d "${body}" $url) + fi + if [ "${SLACK_TOKEN}" != "" -a "${SLACK_CHANNEL}" != "" ]; then + url="https://hooks.slack.com/services/${SLACK_TOKEN}" + txt=$(cat /tmp/$$.txt | sed 's/"/\\"/g;s/$/\\/g' | tr '\n' 'n' ) + payload="payload={\"channel\": \"${SLACK_CHANNEL}\", \"username\": \"clowder\", \"text\": \"${txt}\", \"icon_url\": \"https://opensource.ncsa.illinois.edu/projects/artifacts/CATS/logo.png\"}" + result=$(curl -s -X POST --data-urlencode "${payload}" $url) + fi + if [ "${MSTEAMS_URL}" != "" ]; then + txt=$(cat /tmp/$$.txt | sed 's/"/\\"/g;s/$/\\/g' | tr '\n' 'n' ) + payload="{\"title\": \"UPDATE CLOWDER\", \"text\": \"${txt}\" }" + result=$(curl -s -X POST -H "Content-Type: application/json" -d "${payload}" $MSTEAMS_URL) + fi + if [ "${INFLUXDB_URL}" != "" -a "${INFLUXDB_DATABASE}" != "" -a "${INFLUXDB_USERNAME}" != "" -a "${INFLUXDB_PASSWORD}" != "" ]; then + url="${INFLUXDB_URL}/api/v2/write?bucket=${INFLUXDB_DATABASE}&precision=ns" + tags="host=${HOSTNAME}" + timestamp="$(date -u +"%s%N")" + values="version=\"$( grep '\-Dbuild.version' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )\"" + values="${values},branch=\"$( grep '\-Dbuild.branch' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )\"" + values="${values},build=\"$( grep '\-Dbuild.bamboo' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )\"" + values="${values},gitsha1=\"$( grep '\-Dbuild.gitsha1' clowder/bin/clowder | sed 's/.*=\(.*\)"$/\1/' )\"" + auth="Authorization: Token ${INFLUXDB_USERNAME}:${INFLUXDB_PASSWORD}" + result=$(curl -s -i -XPOST "$url" --header "$auth" --data "clowder_update,${tags} ${values} ${timestamp}") + fi + if [ "${STDOUT}" != "" ]; then + cat /tmp/$$.txt >&3 + fi + rm /tmp/$$.txt fi fi