You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by md...@apache.org on 2018/07/03 20:21:00 UTC
[incubator-openwhisk] 14/19: Re-introduce cli bindings.
This is an automated email from the ASF dual-hosted git repository.
mdeuser pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
commit 34fc4b08f07e4eb4144269fd9759a898365b2912
Author: Rodric Rabbah <ro...@gmail.com>
AuthorDate: Fri Jun 8 23:30:33 2018 -0400
Re-introduce cli bindings.
---
tests/src/test/scala/common/WskCliOperations.scala | 959 +++++++++++++++++++++
tests/src/test/scala/common/WskOperations.scala | 15 +-
2 files changed, 971 insertions(+), 3 deletions(-)
diff --git a/tests/src/test/scala/common/WskCliOperations.scala b/tests/src/test/scala/common/WskCliOperations.scala
new file mode 100644
index 0000000..da3fcbc
--- /dev/null
+++ b/tests/src/test/scala/common/WskCliOperations.scala
@@ -0,0 +1,959 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common
+
+import java.io.File
+import java.time.Instant
+
+import scala.Left
+import scala.Right
+import scala.collection.mutable.Buffer
+import scala.concurrent.duration.Duration
+import scala.concurrent.duration.DurationInt
+import scala.language.postfixOps
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+
+import common.TestUtils._
+import spray.json.JsObject
+import spray.json.JsValue
+import whisk.core.entity.ByteSize
+import whisk.utils.retry
+import common._
+
+import FullyQualifiedNames.fqn
+import FullyQualifiedNames.resolve
+
+/**
+ * Provide Scala bindings for the whisk CLI.
+ *
+ * Each of the top level CLI commands is a "noun" class that extends one
+ * of several traits that are common to the whisk collections and corresponds
+ * to one of the top level CLI nouns.
+ *
+ * Each of the "noun" classes mixes in the RunWskCmd trait which runs arbitrary
+ * wsk commands and returns the results. Optionally RunWskCmd can validate the exit
+ * code matched a desired value.
+ *
+ * The various collections support one or more of these as common traits:
+ * list, get, delete, and sanitize.
+ * Sanitize is akin to delete but accepts a failure because entity may not
+ * exit. Additionally, some of the nouns define custom commands.
+ *
+ * All of the commands define default values that are either optional
+ * or omitted in the common case. This makes for a compact implementation
+ * instead of using a Builder pattern.
+ *
+ * An implicit WskProps instance is required for all of CLI commands. This
+ * type provides the authentication key for the API as well as the namespace.
+ * It also sets the apihost and apiversion explicitly to avoid ambiguity with
+ * a local property file if it exists.
+ */
+class Wsk extends WskOperations {
+ override implicit val action = new CliActionOperations
+ override implicit val trigger = new CliTriggerOperations
+ override implicit val rule = new CliRuleOperations
+ override implicit val activation = new CliActivationOperations
+ override implicit val pkg = new CliPackageOperations
+ override implicit val namespace = new CliNamespaceOperations
+ override implicit val api = new CliGatewayOperations
+}
+
+trait CliListOrGetFromCollectionOperations extends ListOrGetFromCollectionOperations with RunWskCliCommand {
+
+ /**
+ * List entities in collection.
+ *
+ * @param namespace (optional) if specified must be fully qualified namespace
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def list(namespace: Option[String] = None,
+ limit: Option[Int] = None,
+ nameSort: Option[Boolean] = None,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "list", resolve(namespace), "--auth", wp.authKey) ++ {
+ limit map { l =>
+ Seq("--limit", l.toString)
+ } getOrElse Seq()
+ } ++ {
+ nameSort map { n =>
+ Seq("--name-sort")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Gets entity from collection.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def get(name: String,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ summary: Boolean = false,
+ fieldFilter: Option[String] = None,
+ url: Option[Boolean] = None,
+ save: Option[Boolean] = None,
+ saveAs: Option[String] = None)(implicit wp: WskProps): RunResult = {
+
+ val params = Seq(noun, "get", "--auth", wp.authKey) ++
+ Seq(fqn(name)) ++ { if (summary) Seq("--summary") else Seq() } ++ {
+ fieldFilter map { f =>
+ Seq(f)
+ } getOrElse Seq()
+ } ++ {
+ url map { u =>
+ Seq("--url")
+ } getOrElse Seq()
+ } ++ {
+ save map { s =>
+ Seq("--save")
+ } getOrElse Seq()
+ } ++ {
+ saveAs map { s =>
+ Seq("--save-as", s)
+ } getOrElse Seq()
+ }
+
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+}
+
+trait CliDeleteFromCollectionOperations extends DeleteFromCollectionOperations with RunWskCliCommand {
+
+ /**
+ * Deletes entity from collection.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def delete(name: String, expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ cli(wp.overrides ++ Seq(noun, "delete", "--auth", wp.authKey, fqn(name)), expectedExitCode)
+ }
+
+ /**
+ * Deletes entity from collection but does not assert that the command succeeds.
+ * Use this if deleting an entity that may not exist and it is OK if it does not.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ */
+ override def sanitize(name: String)(implicit wp: WskProps): RunResult = {
+ delete(name, DONTCARE_EXIT)
+ }
+}
+
+class CliActionOperations
+ extends CliListOrGetFromCollectionOperations
+ with CliDeleteFromCollectionOperations
+ with HasActivation
+ with ActionOperations {
+
+ override protected val noun = "action"
+
+ /**
+ * Creates action. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def create(
+ name: String,
+ artifact: Option[String],
+ kind: Option[String] = None, // one of docker, copy, sequence or none for autoselect else an explicit type
+ main: Option[String] = None,
+ docker: Option[String] = None,
+ parameters: Map[String, JsValue] = Map(),
+ annotations: Map[String, JsValue] = Map(),
+ parameterFile: Option[String] = None,
+ annotationFile: Option[String] = None,
+ timeout: Option[Duration] = None,
+ memory: Option[ByteSize] = None,
+ logsize: Option[ByteSize] = None,
+ shared: Option[Boolean] = None,
+ update: Boolean = false,
+ web: Option[String] = None,
+ websecure: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, if (!update) "create" else "update", "--auth", wp.authKey, fqn(name)) ++ {
+ artifact map { Seq(_) } getOrElse Seq()
+ } ++ {
+ kind map { k =>
+ if (k == "sequence" || k == "copy" || k == "native") Seq(s"--$k")
+ else Seq("--kind", k)
+ } getOrElse Seq()
+ } ++ {
+ main.toSeq flatMap { p =>
+ Seq("--main", p)
+ }
+ } ++ {
+ docker.toSeq flatMap { p =>
+ Seq("--docker", p)
+ }
+ } ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ annotations flatMap { p =>
+ Seq("-a", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ parameterFile map { pf =>
+ Seq("-P", pf)
+ } getOrElse Seq()
+ } ++ {
+ annotationFile map { af =>
+ Seq("-A", af)
+ } getOrElse Seq()
+ } ++ {
+ timeout map { t =>
+ Seq("-t", t.toMillis.toString)
+ } getOrElse Seq()
+ } ++ {
+ memory map { m =>
+ Seq("-m", m.toMB.toString)
+ } getOrElse Seq()
+ } ++ {
+ logsize map { l =>
+ Seq("-l", l.toMB.toString)
+ } getOrElse Seq()
+ } ++ {
+ shared map { s =>
+ Seq("--shared", if (s) "yes" else "no")
+ } getOrElse Seq()
+ } ++ {
+ web map { w =>
+ Seq("--web", w)
+ } getOrElse Seq()
+ } ++ {
+ websecure map { ws =>
+ Seq("--web-secure", ws)
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Invokes action. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def invoke(name: String,
+ parameters: Map[String, JsValue] = Map(),
+ parameterFile: Option[String] = None,
+ blocking: Boolean = false,
+ result: Boolean = false,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "invoke", "--auth", wp.authKey, fqn(name)) ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ parameterFile map { pf =>
+ Seq("-P", pf)
+ } getOrElse Seq()
+ } ++ { if (blocking) Seq("--blocking") else Seq() } ++ { if (result) Seq("--result") else Seq() }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+}
+
+class CliTriggerOperations
+ extends CliListOrGetFromCollectionOperations
+ with CliDeleteFromCollectionOperations
+ with HasActivation
+ with TriggerOperations {
+
+ override protected val noun = "trigger"
+
+ /**
+ * Creates trigger. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def create(name: String,
+ parameters: Map[String, JsValue] = Map(),
+ annotations: Map[String, JsValue] = Map(),
+ parameterFile: Option[String] = None,
+ annotationFile: Option[String] = None,
+ feed: Option[String] = None,
+ shared: Option[Boolean] = None,
+ update: Boolean = false,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, if (!update) "create" else "update", "--auth", wp.authKey, fqn(name)) ++ {
+ feed map { f =>
+ Seq("--feed", fqn(f))
+ } getOrElse Seq()
+ } ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ annotations flatMap { p =>
+ Seq("-a", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ parameterFile map { pf =>
+ Seq("-P", pf)
+ } getOrElse Seq()
+ } ++ {
+ annotationFile map { af =>
+ Seq("-A", af)
+ } getOrElse Seq()
+ } ++ {
+ shared map { s =>
+ Seq("--shared", if (s) "yes" else "no")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Fires trigger. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def fire(name: String,
+ parameters: Map[String, JsValue] = Map(),
+ parameterFile: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "fire", "--auth", wp.authKey, fqn(name)) ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ parameterFile map { pf =>
+ Seq("-P", pf)
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+}
+
+class CliRuleOperations
+ extends CliListOrGetFromCollectionOperations
+ with CliDeleteFromCollectionOperations
+ with WaitFor
+ with RuleOperations {
+
+ override protected val noun = "rule"
+
+ /**
+ * Creates rule. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param trigger must be a simple name
+ * @param action must be a simple name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def create(name: String,
+ trigger: String,
+ action: String,
+ annotations: Map[String, JsValue] = Map(),
+ shared: Option[Boolean] = None,
+ update: Boolean = false,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, if (!update) "create" else "update", "--auth", wp.authKey, fqn(name), (trigger), (action)) ++ {
+ annotations flatMap { p =>
+ Seq("-a", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ shared map { s =>
+ Seq("--shared", if (s) "yes" else "no")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Deletes rule.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def delete(name: String, expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ super.delete(name, expectedExitCode)
+ }
+
+ /**
+ * Enables rule.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def enable(name: String, expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ cli(wp.overrides ++ Seq(noun, "enable", "--auth", wp.authKey, fqn(name)), expectedExitCode)
+ }
+
+ /**
+ * Disables rule.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def disable(name: String, expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ cli(wp.overrides ++ Seq(noun, "disable", "--auth", wp.authKey, fqn(name)), expectedExitCode)
+ }
+
+ /**
+ * Checks state of rule.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def state(name: String, expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ cli(wp.overrides ++ Seq(noun, "status", "--auth", wp.authKey, fqn(name)), expectedExitCode)
+ }
+}
+
+class CliActivationOperations extends ActivationOperations with RunWskCliCommand with HasActivation with WaitFor {
+
+ protected val noun = "activation"
+
+ /**
+ * Activation polling console.
+ *
+ * @param duration exits console after duration
+ * @param since (optional) time travels back to activation since given duration
+ * @param actionName (optional) name of entity to filter activation records on.
+ */
+ override def console(duration: Duration,
+ since: Option[Duration] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ actionName: Option[String] = None)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "poll") ++ {
+ actionName map { name =>
+ Seq(name)
+ } getOrElse Seq()
+ } ++ Seq("--auth", wp.authKey, "--exit", duration.toSeconds.toString) ++ {
+ since map { s =>
+ Seq("--since-seconds", s.toSeconds.toString)
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Lists activations.
+ *
+ * @param filter (optional) if define, must be a simple entity name
+ * @param limit (optional) the maximum number of activation to return
+ * @param since (optional) only the activations since this timestamp are included
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ def list(filter: Option[String] = None,
+ limit: Option[Int] = None,
+ since: Option[Instant] = None,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "list", "--auth", wp.authKey) ++ { filter map { Seq(_) } getOrElse Seq() } ++ {
+ limit map { l =>
+ Seq("--limit", l.toString)
+ } getOrElse Seq()
+ } ++ {
+ since map { i =>
+ Seq("--since", i.toEpochMilli.toString)
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Parses result of WskActivation.list to extract sequence of activation ids.
+ *
+ * @param rr run result, should be from WhiskActivation.list otherwise behavior is undefined
+ * @return sequence of activations
+ */
+ def ids(rr: RunResult): Seq[String] = {
+ rr.stdout.split("\n") filter {
+ // remove empty lines the header
+ s =>
+ s.nonEmpty && s != "activations"
+ } map {
+ // split into (id, name)
+ _.split(" ")(0)
+ }
+ }
+
+ /**
+ * Gets activation by id.
+ *
+ * @param activationId the activation id
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ * @param last retrieves latest acitvation
+ */
+ override def get(activationId: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ fieldFilter: Option[String] = None,
+ last: Option[Boolean] = None,
+ summary: Option[Boolean] = None)(implicit wp: WskProps): RunResult = {
+ val params = {
+ activationId map { a =>
+ Seq(a)
+ } getOrElse Seq()
+ } ++ {
+ fieldFilter map { f =>
+ Seq(f)
+ } getOrElse Seq()
+ } ++ {
+ last map { l =>
+ Seq("--last")
+ } getOrElse Seq()
+ } ++ {
+ summary map { s =>
+ Seq("--summary")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ Seq(noun, "get", "--auth", wp.authKey) ++ params, expectedExitCode)
+ }
+
+ /**
+ * Gets activation logs by id.
+ *
+ * @param activationId the activation id
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ * @param last retrieves latest acitvation
+ */
+ override def logs(activationId: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ last: Option[Boolean] = None)(implicit wp: WskProps): RunResult = {
+ val params = {
+ activationId map { a =>
+ Seq(a)
+ } getOrElse Seq()
+ } ++ {
+ last map { l =>
+ Seq("--last")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ Seq(noun, "logs", "--auth", wp.authKey) ++ params, expectedExitCode)
+ }
+
+ /**
+ * Gets activation result by id.
+ *
+ * @param activationId the activation id
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ * @param last retrieves latest acitvation
+ */
+ override def result(activationId: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ last: Option[Boolean] = None)(implicit wp: WskProps): RunResult = {
+ val params = {
+ activationId map { a =>
+ Seq(a)
+ } getOrElse Seq()
+ } ++ {
+ last map { l =>
+ Seq("--last")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ Seq(noun, "result", "--auth", wp.authKey) ++ params, expectedExitCode)
+ }
+
+ /**
+ * Polls activations list for at least N activations. The activations
+ * are optionally filtered for the given entity. Will return as soon as
+ * N activations are found. If after retry budget is exhausted, N activations
+ * are still not present, will return a partial result. Hence caller must
+ * check length of the result and not assume it is >= N.
+ *
+ * @param N the number of activations desired
+ * @param entity the name of the entity to filter from activation list
+ * @param limit the maximum number of entities to list (if entity name is not unique use Some(0))
+ * @param since (optional) only the activations since this timestamp are included
+ * @param retries the maximum retries (total timeout is retries + 1 seconds)
+ * @return activation ids found, caller must check length of sequence
+ */
+ override def pollFor(N: Int,
+ entity: Option[String],
+ limit: Option[Int] = None,
+ since: Option[Instant] = None,
+ retries: Int = 10,
+ pollPeriod: Duration = 1.second)(implicit wp: WskProps): Seq[String] = {
+ Try {
+ retry({
+ val result = ids(list(filter = entity, limit = limit, since = since))
+ if (result.length >= N) result else throw PartialResult(result)
+ }, retries, waitBeforeRetry = Some(pollPeriod))
+ } match {
+ case Success(ids) => ids
+ case Failure(PartialResult(ids)) => ids
+ case _ => Seq()
+ }
+ }
+
+ /**
+ * Polls for an activation matching the given id. If found
+ * return Right(activation) else Left(result of running CLI command).
+ *
+ * @return either Left(error message) or Right(activation as JsObject)
+ */
+ override def waitForActivation(activationId: String,
+ initialWait: Duration = 1 second,
+ pollPeriod: Duration = 1 second,
+ totalWait: Duration = 30 seconds)(implicit wp: WskProps): Either[String, JsObject] = {
+ val activation = waitfor(
+ () => {
+ val result =
+ cli(wp.overrides ++ Seq(noun, "get", activationId, "--auth", wp.authKey), expectedExitCode = DONTCARE_EXIT)
+ if (result.exitCode == NOT_FOUND) {
+ null
+ } else if (result.exitCode == SUCCESS_EXIT) {
+ Right(result.stdout)
+ } else Left(s"$result")
+ },
+ initialWait,
+ pollPeriod,
+ totalWait)
+
+ Option(activation) map {
+ case Right(stdout) =>
+ Try {
+ // strip first line and convert the rest to JsObject
+ assert(stdout.startsWith("ok: got activation"))
+ WskOperations.parseJsonString(stdout)
+ } map {
+ Right(_)
+ } getOrElse Left(s"cannot parse activation from '$stdout'")
+ case Left(error) => Left(error)
+ } getOrElse Left(s"$activationId not found")
+ }
+
+ /** Used in polling for activations to record partial results from retry poll. */
+ private case class PartialResult(ids: Seq[String]) extends Throwable
+}
+
+class CliNamespaceOperations extends CliDeleteFromCollectionOperations with NamespaceOperations with RunWskCliCommand {
+
+ protected val noun = "namespace"
+
+ /**
+ * Lists available namespaces for whisk key.
+ *
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def list(expectedExitCode: Int = SUCCESS_EXIT, nameSort: Option[Boolean] = None)(
+ implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "list", "--auth", wp.authKey) ++ {
+ nameSort map { n =>
+ Seq("--name-sort")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Looks up namespace for whisk props.
+ *
+ * @param wskprops instance of WskProps with an auth key to lookup
+ * @return namespace as string
+ */
+ override def whois()(implicit wskprops: WskProps): String = {
+ // the invariant that list() returns a conforming result is enforced in WskRestBasicTests
+ val ns = list().stdout.lines.toSeq.last.trim
+ assert(ns != "_") // this is not permitted
+ ns
+ }
+
+ /**
+ * Gets entities in namespace.
+ *
+ * @param namespace (optional) if specified must be fully qualified namespace
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ def get(namespace: Option[String] = None, expectedExitCode: Int, nameSort: Option[Boolean] = None)(
+ implicit wp: WskProps): RunResult = {
+ val params = {
+ nameSort map { n =>
+ Seq("--name-sort")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ Seq(noun, "get", resolve(namespace), "--auth", wp.authKey) ++ params, expectedExitCode)
+ }
+}
+
+class CliPackageOperations
+ extends CliListOrGetFromCollectionOperations
+ with CliDeleteFromCollectionOperations
+ with PackageOperations {
+ override protected val noun = "package"
+
+ /**
+ * Creates package. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def create(name: String,
+ parameters: Map[String, JsValue] = Map(),
+ annotations: Map[String, JsValue] = Map(),
+ parameterFile: Option[String] = None,
+ annotationFile: Option[String] = None,
+ shared: Option[Boolean] = None,
+ update: Boolean = false,
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, if (!update) "create" else "update", "--auth", wp.authKey, fqn(name)) ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ annotations flatMap { p =>
+ Seq("-a", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ parameterFile map { pf =>
+ Seq("-P", pf)
+ } getOrElse Seq()
+ } ++ {
+ annotationFile map { af =>
+ Seq("-A", af)
+ } getOrElse Seq()
+ } ++ {
+ shared map { s =>
+ Seq("--shared", if (s) "yes" else "no")
+ } getOrElse Seq()
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+
+ /**
+ * Binds package. Parameters mirror those available in the CLI.
+ *
+ * @param name either a fully qualified name or a simple entity name
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def bind(provider: String,
+ name: String,
+ parameters: Map[String, JsValue] = Map(),
+ annotations: Map[String, JsValue] = Map(),
+ expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "bind", "--auth", wp.authKey, fqn(provider), fqn(name)) ++ {
+ parameters flatMap { p =>
+ Seq("-p", p._1, p._2.compactPrint)
+ }
+ } ++ {
+ annotations flatMap { p =>
+ Seq("-a", p._1, p._2.compactPrint)
+ }
+ }
+ cli(wp.overrides ++ params, expectedExitCode)
+ }
+}
+
+class CliGatewayOperations extends GatewayOperations with RunWskCliCommand {
+ protected val noun = "api"
+
+ /**
+ * Creates and API endpoint. Parameters mirror those available in the CLI.
+ *
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def create(basepath: Option[String] = None,
+ relpath: Option[String] = None,
+ operation: Option[String] = None,
+ action: Option[String] = None,
+ apiname: Option[String] = None,
+ swagger: Option[String] = None,
+ responsetype: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ cliCfgFile: Option[String] = None)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "create", "--auth", wp.authKey) ++ {
+ basepath map { b =>
+ Seq(b)
+ } getOrElse Seq()
+ } ++ {
+ relpath map { r =>
+ Seq(r)
+ } getOrElse Seq()
+ } ++ {
+ operation map { o =>
+ Seq(o)
+ } getOrElse Seq()
+ } ++ {
+ action map { aa =>
+ Seq(aa)
+ } getOrElse Seq()
+ } ++ {
+ apiname map { a =>
+ Seq("--apiname", a)
+ } getOrElse Seq()
+ } ++ {
+ swagger map { s =>
+ Seq("--config-file", s)
+ } getOrElse Seq()
+ } ++ {
+ responsetype map { t =>
+ Seq("--response-type", t)
+ } getOrElse Seq()
+ }
+ cli(
+ wp.overrides ++ params,
+ expectedExitCode,
+ showCmd = true,
+ env = Map("WSK_CONFIG_FILE" -> cliCfgFile.getOrElse("")))
+ }
+
+ /**
+ * Retrieve a list of API endpoints. Parameters mirror those available in the CLI.
+ *
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def list(basepathOrApiName: Option[String] = None,
+ relpath: Option[String] = None,
+ operation: Option[String] = None,
+ limit: Option[Int] = None,
+ since: Option[Instant] = None,
+ full: Option[Boolean] = None,
+ nameSort: Option[Boolean] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ cliCfgFile: Option[String] = None)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "list", "--auth", wp.authKey) ++ {
+ basepathOrApiName map { b =>
+ Seq(b)
+ } getOrElse Seq()
+ } ++ {
+ relpath map { r =>
+ Seq(r)
+ } getOrElse Seq()
+ } ++ {
+ operation map { o =>
+ Seq(o)
+ } getOrElse Seq()
+ } ++ {
+ limit map { l =>
+ Seq("--limit", l.toString)
+ } getOrElse Seq()
+ } ++ {
+ since map { i =>
+ Seq("--since", i.toEpochMilli.toString)
+ } getOrElse Seq()
+ } ++ {
+ full map { r =>
+ Seq("--full")
+ } getOrElse Seq()
+ } ++ {
+ nameSort map { n =>
+ Seq("--name-sort")
+ } getOrElse Seq()
+ }
+ cli(
+ wp.overrides ++ params,
+ expectedExitCode,
+ showCmd = true,
+ env = Map("WSK_CONFIG_FILE" -> cliCfgFile.getOrElse("")))
+ }
+
+ /**
+ * Retieves an API's configuration. Parameters mirror those available in the CLI.
+ * Runs a command wsk [params] where the arguments come in as a sequence.
+ *
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def get(basepathOrApiName: Option[String] = None,
+ full: Option[Boolean] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ cliCfgFile: Option[String] = None,
+ format: Option[String] = None)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "get", "--auth", wp.authKey) ++ {
+ basepathOrApiName map { b =>
+ Seq(b)
+ } getOrElse Seq()
+ } ++ {
+ full map { f =>
+ if (f) Seq("--full") else Seq()
+ } getOrElse Seq()
+ } ++ {
+ format map { ft =>
+ Seq("--format", ft)
+ } getOrElse Seq()
+ }
+ cli(
+ wp.overrides ++ params,
+ expectedExitCode,
+ showCmd = true,
+ env = Map("WSK_CONFIG_FILE" -> cliCfgFile.getOrElse("")))
+ }
+
+ /**
+ * Delete an entire API or a subset of API endpoints. Parameters mirror those available in the CLI.
+ *
+ * @param expectedExitCode (optional) the expected exit code for the command
+ * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+ */
+ override def delete(basepathOrApiName: String,
+ relpath: Option[String] = None,
+ operation: Option[String] = None,
+ expectedExitCode: Int = SUCCESS_EXIT,
+ cliCfgFile: Option[String] = None)(implicit wp: WskProps): RunResult = {
+ val params = Seq(noun, "delete", "--auth", wp.authKey, basepathOrApiName) ++ {
+ relpath map { r =>
+ Seq(r)
+ } getOrElse Seq()
+ } ++ {
+ operation map { o =>
+ Seq(o)
+ } getOrElse Seq()
+ }
+ cli(
+ wp.overrides ++ params,
+ expectedExitCode,
+ showCmd = true,
+ env = Map("WSK_CONFIG_FILE" -> cliCfgFile.getOrElse("")))
+ }
+}
+
+trait RunWskCliCommand extends RunCliCmd {
+ private val binaryName = "wsk"
+ private val cliPath = if (WhiskProperties.useCLIDownload) getDownloadedGoCLIPath else WhiskProperties.getCLIPath
+
+ assert((new File(cliPath)).exists, s"did not find $cliPath")
+
+ /** What is the path to a downloaded CLI? **/
+ private def getDownloadedGoCLIPath = {
+ s"${System.getProperty("user.home")}${File.separator}.local${File.separator}bin${File.separator}${binaryName}"
+ }
+
+ def baseCommand = Buffer(cliPath)
+}
diff --git a/tests/src/test/scala/common/WskOperations.scala b/tests/src/test/scala/common/WskOperations.scala
index 9aa1564..2597c59 100644
--- a/tests/src/test/scala/common/WskOperations.scala
+++ b/tests/src/test/scala/common/WskOperations.scala
@@ -152,9 +152,18 @@ trait WskOperations {
val namespace: NamespaceOperations
val api: GatewayOperations
- /*
- * Utility function to return a JSON object from the CLI output that returns
- * an optional a status line following by the JSON data
+ /**
+ * Utility function which strips the leading line if it ends in a newline (present when output is from
+ * wsk CLI) and parses the rest as a JSON object.
+ */
+ def parseJsonString(jsonStr: String): JsObject = WskOperations.parseJsonString(jsonStr)
+}
+
+object WskOperations {
+
+ /**
+ * Utility function which strips the leading line if it ends in a newline (present when output is from
+ * wsk CLI) and parses the rest as a JSON object.
*/
def parseJsonString(jsonStr: String): JsObject = {
jsonStr.substring(jsonStr.indexOf("\n") + 1).parseJson.asJsObject // Skip optional status line before parsing