Skip to content

Commit 92a99e4

Browse files
committed
Forward header from a trigger to actions.
1 parent 639c4a9 commit 92a99e4

File tree

4 files changed

+118
-8
lines changed

4 files changed

+118
-8
lines changed

core/controller/src/main/scala/org/apache/openwhisk/core/controller/Triggers.scala

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package org.apache.openwhisk.core.controller
1919

2020
import java.time.{Clock, Instant}
21-
2221
import scala.collection.immutable.Map
2322
import scala.concurrent.Future
2423
import scala.util.Try
@@ -28,7 +27,7 @@ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
2827
import akka.http.scaladsl.model.HttpMethods.POST
2928
import akka.http.scaladsl.model.StatusCodes.{Accepted, BadRequest, InternalServerError, NoContent, OK, ServerError}
3029
import akka.http.scaladsl.model.Uri.Path
31-
import akka.http.scaladsl.model.headers.Authorization
30+
import akka.http.scaladsl.model.headers.{`Timeout-Access`, Authorization}
3231
import akka.http.scaladsl.model._
3332
import akka.http.scaladsl.server.{RequestContext, RouteResult}
3433
import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller}
@@ -151,7 +150,19 @@ trait WhiskTriggersApi extends WhiskCollectionAPI {
151150
response = ActivationResponse.success(payload orElse Some(JsObject.empty)),
152151
version = trigger.version,
153152
duration = None)
154-
val args: JsObject = trigger.parameters.merge(payload).getOrElse(JsObject.empty)
153+
val headers = JsObject(
154+
Map(new WebApiDirectives().headers -> request.headers
155+
.collect {
156+
case h if h.name != `Timeout-Access`.name => h.lowercaseName -> h.value
157+
}
158+
.toMap
159+
.toJson))
160+
161+
val mergedPayload = Some {
162+
(headers.fields ++ (payload getOrElse JsObject.empty).fields).toJson.asJsObject
163+
}
164+
165+
val args: JsObject = trigger.parameters.merge(mergedPayload).getOrElse(JsObject.empty)
155166

156167
activateRules(user, args, trigger.rules.getOrElse(Map.empty))
157168
.map(results => triggerActivation.withLogs(ActivationLogs(results.map(_.toJson.compactPrint).toVector)))

tests/dat/actions/params.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function main(args) {
2+
return {args}
3+
}

tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskRestBasicUsageTests.scala

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import akka.http.scaladsl.model.StatusCodes.NotFound
2121
import akka.http.scaladsl.model.StatusCodes.OK
2222
import akka.http.scaladsl.model.StatusCodes.BadRequest
2323
import akka.http.scaladsl.model.StatusCodes.Conflict
24+
2425
import java.time.Instant
2526
import java.time.Clock
26-
2727
import scala.language.postfixOps
2828
import scala.concurrent.duration.DurationInt
2929
import scala.util.Random
@@ -36,22 +36,29 @@ import common.WhiskProperties
3636
import common.WskProps
3737
import common.WskTestHelpers
3838
import common.WskActorSystem
39-
import common.rest.WskRestOperations
39+
import common.rest.{RestResult, RunRestCmd, WskRestOperations}
4040
import spray.json.DefaultJsonProtocol._
4141
import spray.json._
4242
import org.apache.openwhisk.core.entity._
4343
import org.apache.openwhisk.core.entity.size.SizeInt
4444
import TestJsonArgs._
45+
import akka.http.scaladsl.Http
46+
import akka.http.scaladsl.model.HttpMethods.POST
47+
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpHeader, HttpMethod, HttpRequest, HttpResponse, Uri}
48+
import akka.http.scaladsl.model.Uri.{Path, Query}
49+
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials, RawHeader}
50+
import akka.util.ByteString
4551
import org.apache.openwhisk.http.Messages
4652

4753
/**
4854
* Tests for basic CLI usage. Some of these tests require a deployed backend.
4955
*/
5056
@RunWith(classOf[JUnitRunner])
51-
class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskActorSystem {
57+
class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskActorSystem with RunRestCmd {
5258

5359
implicit val wskprops = WskProps()
54-
val wsk = new WskRestOperations
60+
implicit lazy override val executionContext = actorSystem.dispatcher
61+
val wsk = new WskRestOperations()
5562
val defaultAction: Some[String] = Some(TestUtils.getTestActionFilename("hello.js"))
5663
val usrAgentHeaderRegEx: String = """\bUser-Agent\b": \[\s+"OpenWhisk\-CLI/1.\d+.*"""
5764

@@ -737,4 +744,86 @@ class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskAct
737744
wsk.trigger.delete(triggerName).statusCode shouldBe OK
738745
}
739746
}
747+
748+
it should "forward headers as parameters to the associated action" in withAssetCleaner(wskprops) {
749+
(wp, assetHelper) =>
750+
val guestNamespace = wsk.namespace.whois()
751+
val name = "triggerWithHeaders"
752+
val actionName = "params"
753+
val ruleName = "ruleWithHeaders"
754+
755+
assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
756+
trigger.create(name)
757+
}
758+
759+
assetHelper.withCleaner(wsk.action, actionName) { (action, _) =>
760+
action.create(actionName, Some(TestUtils.getTestActionFilename("params.js")))
761+
}
762+
763+
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, _) =>
764+
rule.create(ruleName, trigger = name, action = actionName)
765+
rule.enable(ruleName)
766+
}
767+
768+
val path = Path(s"$basePath/namespaces/$guestNamespace/triggers/$name")
769+
770+
val resp = requestEntityWithHeader(POST, path, List(RawHeader("Foo", "Bar")))(wp)
771+
val result = new RestResult(resp.status.intValue, getTransactionId(resp), getRespData(resp))
772+
773+
withActivation(wsk.activation, result) { triggerActivation =>
774+
val ruleActivation = triggerActivation.logs.get.map(_.parseJson.convertTo[common.RuleActivationResult]).head
775+
withActivation(wsk.activation, ruleActivation.activationId) { actionActivation =>
776+
actionActivation.response.result match {
777+
case Some(result) =>
778+
result.fields.get("args") map { headers =>
779+
headers.asJsObject.fields.get("__ow_headers") map { params =>
780+
params.asJsObject.fields.get("foo") map { foo =>
781+
foo shouldBe JsString("Bar")
782+
}
783+
}
784+
}
785+
786+
case others =>
787+
fail(s"no result found: $others")
788+
789+
}
790+
actionActivation.cause shouldBe None
791+
}
792+
}
793+
}
794+
795+
def requestEntityWithHeader(method: HttpMethod,
796+
path: Path,
797+
headers: List[HttpHeader],
798+
params: Map[String, String] = Map.empty,
799+
body: Option[String] = None)(implicit wp: WskProps): HttpResponse = {
800+
val credentials = wp.authKey.split(":")
801+
val creds = new BasicHttpCredentials(credentials(0), credentials(1))
802+
803+
// startsWith(http) includes https
804+
val hostWithScheme = if (wp.apihost.startsWith("http")) {
805+
Uri(wp.apihost)
806+
} else {
807+
Uri().withScheme("https").withHost(wp.apihost)
808+
}
809+
810+
val request = HttpRequest(
811+
method,
812+
hostWithScheme.withPath(path).withQuery(Query(params)),
813+
Authorization(creds) :: headers,
814+
entity =
815+
body.map(b => HttpEntity.Strict(ContentTypes.`application/json`, ByteString(b))).getOrElse(HttpEntity.Empty))
816+
val response = Http().singleRequest(request, connectionContext).flatMap { _.toStrict(toStrictTimeout) }.futureValue
817+
818+
logger.debug(this, s"Request: $request")
819+
logger.debug(this, s"Response: $response")
820+
821+
val validationErrors = validateRequestAndResponse(request, response)
822+
if (validationErrors.nonEmpty) {
823+
fail(
824+
s"HTTP request or response did not match the Swagger spec.\nRequest: $request\n" +
825+
s"Response: $response\nValidation Error: $validationErrors")
826+
}
827+
response
828+
}
740829
}

tests/src/test/scala/system/basic/WskSequenceTests.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,14 @@ class WskSequenceTests extends TestHelpers with WskTestHelpers with StreamLoggin
551551
withActivation(wsk.activation, triggerFireRun) { triggerActivation =>
552552
val ruleActivation = triggerActivation.logs.get.map(_.parseJson.convertTo[RuleActivationResult]).head
553553
withActivation(wsk.activation, ruleActivation.activationId) { actionActivation =>
554-
actionActivation.response.result shouldBe Some(triggerPayload)
554+
actionActivation.response.result match {
555+
case Some(result) =>
556+
val (_, part2) = result.fields partition (p => p._1 == "__ow_headers") // excluding headers
557+
JsObject(part2) shouldBe triggerPayload
558+
case others =>
559+
fail(s"no result found: $others")
560+
561+
}
555562
actionActivation.cause shouldBe None
556563
}
557564
}

0 commit comments

Comments
 (0)