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 <[email protected]> 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
