Skip to content

Commit

Permalink
Allow CLI to sort entities by name (#2326)
Browse files Browse the repository at this point in the history
- Created interfaces `Printables` and `Sortables`
- Made Actions, Triggers, Packages, Rules, APIs into Printables and Sortables
- Made Activations into Printables and Sortables, Sort currently undefined
- Made alphabetic sorting default, sort by last update time with --time flag
- Changed sorting default back to last update time, --sort flag for alphabetical sorting
- Updated flag name to "--name-sort"/"-n"
- Updated Docs
- Fixed rule status printing for `wsk list` and `wsk namespace get`
  • Loading branch information
underwoodb-sd-ibm authored and rabbah committed Aug 11, 2017
1 parent c2c44c3 commit fe42018
Show file tree
Hide file tree
Showing 24 changed files with 750 additions and 158 deletions.
28 changes: 26 additions & 2 deletions docs/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1028,13 +1028,37 @@ This command starts a polling loop that continuously checks for logs from activa

## Listing actions

You can list all the actions that you have created using:
You can list all the actions that you have created using `wsk action list`:

```
wsk action list
actions
/guest/packageB/A private nodejs:6
/guest/C private nodejs:6
/guest/A private nodejs:6
/guest/packageA/B private nodejs:6
/guest/packageA/A private nodejs:6
/guest/B private nodejs:6
```

As you write more actions, this list gets longer and it can be helpful to group related actions into [packages](./packages.md). To filter your list of actions to just the those within a specific pacakge, you can use:
Here, we see actions listed in order from most to least recently updated. For easier browsing, you can use the flag `--name-sort` or `-n` to sort the list alphabetically:

```
wsk action list --name-sort
actions
/guest/A private nodejs:6
/guest/B private nodejs:6
/guest/C private nodejs:6
/guest/packageA/A private nodejs:6
/guest/packageA/B private nodejs:6
/guest/packageB/A private nodejs:6
```

Notice that the list is now sorted alphabetically by namespace, then package name, and finally action name, with the default package (no specified package) listed at the top.

**Note**: The printed list is sorted alphabetically after it is received from the server. Other list flags such as `--limit` and `--skip` will be applied to the block of actions before they are received for sorting. To list actions in order by creation time, use the flag `--time`.

As you write more actions, this list gets longer and it can be helpful to group related actions into [packages](./packages.md). To filter your list of actions to just those within a specific package, you can use:

```
wsk action list [PACKAGE NAME]
Expand Down
30 changes: 15 additions & 15 deletions docs/apigateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ Follow the instructions in [Configure CLI](./README.md#setting-up-the-openwhisk-
return {payload: `Hello world ${name}`};
}
```

2. Create a web action from the following JavaScript function. For this example, the action is called 'hello'. Make sure to add the flag `--web true`

```
wsk action create hello hello.js --web true
```
```
ok: created action hello
```

3. Create an API with base path `/hello`, path `/world` and method `get` with response type `json`

```
wsk api create /hello /world get hello --response-type json
```
Expand All @@ -42,9 +42,9 @@ Follow the instructions in [Configure CLI](./README.md#setting-up-the-openwhisk-
https://${APIHOST}:9001/api/21ef035/hello/world
```
A new URL is generated exposing the `hello` action via a __GET__ HTTP method.

4. Let's give it a try by sending a HTTP request to the URL.

```
$ curl https://${APIHOST}:9001/api/21ef035/hello/world?name=OpenWhisk
```
Expand All @@ -54,27 +54,27 @@ Follow the instructions in [Configure CLI](./README.md#setting-up-the-openwhisk-
}
```
The web action `hello` was invoked, returning back a JSON object including the parameter `name` sent via query parameter. You can pass parameters to the action via simple query parameters, or via the request body. Web actions allow you to invoke an action in a public way without the OpenWhisk authorization API key.

### Full control over the HTTP response
The `--response-type` flag controls the target URL of the web action to be proxied by the API Gateway. Using `--response-type json` as above returns the full result of the action in JSON format and automatically sets the Content-Type header to `application/json` which enables you to easily get started.
Once you get started you want to have full control over the HTTP response properties like `statusCode`, `headers` and return different content types in the `body`. You can do this by using `--response-type http`, this will configure the target URL of the web action with the `http` extension.

The `--response-type` flag controls the target URL of the web action to be proxied by the API Gateway. Using `--response-type json` as above returns the full result of the action in JSON format and automatically sets the Content-Type header to `application/json` which enables you to easily get started.

Once you get started, you will want to have full control over the HTTP response properties like `statusCode`, `headers` and return different content types in the `body`. You can do this by using `--response-type http`, this will configure the target URL of the web action with the `http` extension.

You can choose to change the code of the action to comply with the return of web actions with `http` extension or include the action in a sequence passing its result to a new action that transforms the result to be properly formatted for an HTTP response. You can read more about response types and web actions extensions in the [Web Actions](webactions.md) documentation.

Change the code for the `hello.js` returning the JSON properties `body`, `statusCode` and `headers`
```javascript
function main({name:name='Serverless API'}) {
return {
body: new Buffer(JSON.stringify({payload:`Hello world ${name}`})).toString('base64'),
statusCode:200,
body: new Buffer(JSON.stringify({payload:`Hello world ${name}`})).toString('base64'),
statusCode: 200,
headers:{ 'Content-Type': 'application/json'}
};
}
```
Notice that the body needs to be return encoded in `base64` and not a string.

Update the action with the modified result
```
wsk action update hello hello.js --web true
Expand Down Expand Up @@ -170,7 +170,7 @@ curl -X GET https://${APIHOST}:9001/api/21ef035/club/books
```

### Exporting the configuration
Let's export API named `Book Club` into a file that we can use as a base to to re-create the APIs using a file as input.
Let's export API named `Book Club` into a file that we can use as a base to to re-create the APIs using a file as input.
```
wsk api get "Book Club" > club-swagger.json
```
Expand Down
25 changes: 18 additions & 7 deletions tests/src/test/scala/common/Wsk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ trait ListOrGetFromCollection extends FullyQualifiedNames {
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() }
{ limit map { l => Seq("--limit", l.toString) } getOrElse Seq() } ++
{ nameSort map { n => Seq("--name-sort") } getOrElse Seq() }
cli(wp.overrides ++ params, expectedExitCode)
}

Expand Down Expand Up @@ -690,9 +692,12 @@ class WskNamespace()
* @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(expectedExitCode: Int = SUCCESS_EXIT)(
def list(
expectedExitCode: Int = SUCCESS_EXIT,
nameSort: Option[Boolean] = None)(
implicit wp: WskProps): RunResult = {
val params = Seq(noun, "list", "--auth", wp.authKey)
val params = Seq(noun, "list", "--auth", wp.authKey) ++
{ nameSort map { n => Seq("--name-sort") } getOrElse Seq() }
cli(wp.overrides ++ params, expectedExitCode)
}

Expand All @@ -718,9 +723,11 @@ class WskNamespace()
*/
def get(
namespace: Option[String] = None,
expectedExitCode: Int)(
expectedExitCode: Int,
nameSort: Option[Boolean] = None)(
implicit wp: WskProps): RunResult = {
cli(wp.overrides ++ Seq(noun, "get", resolve(namespace), "--auth", wp.authKey), expectedExitCode)
val params = { nameSort map { n => Seq("--name-sort") } getOrElse Seq() }
cli(wp.overrides ++ Seq(noun, "get", resolve(namespace), "--auth", wp.authKey) ++ params, expectedExitCode)
}
}

Expand Down Expand Up @@ -818,6 +825,7 @@ class WskApiExperimental extends RunWskCmd {
limit: Option[Int] = None,
since: Option[Instant] = None,
full: Option[Boolean] = None,
nameSort: Option[Boolean] = None,
expectedExitCode: Int = SUCCESS_EXIT)(
implicit wp: WskProps): RunResult = {
val params = Seq(noun, "list", "--auth", wp.authKey) ++
Expand All @@ -826,7 +834,8 @@ class WskApiExperimental extends RunWskCmd {
{ 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() }
{ full map { r => Seq("--full") } getOrElse Seq() } ++
{ nameSort map { n => Seq("--name-sort") } getOrElse Seq() }
cli(wp.overrides ++ params, expectedExitCode, showCmd = true)
}

Expand Down Expand Up @@ -912,6 +921,7 @@ class WskApi()
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 = {
Expand All @@ -921,7 +931,8 @@ class WskApi()
{ 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() }
{ 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("")))
}

Expand Down
39 changes: 38 additions & 1 deletion tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,12 @@ class ApiGwTests
limit: Option[Int] = None,
since: Option[Instant] = None,
full: Option[Boolean] = None,
nameSort: Option[Boolean] = None,
expectedExitCode: Int = SUCCESS_EXIT,
cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {

checkThrottle()
wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, expectedExitCode, cliCfgFile)
wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, nameSort, expectedExitCode, cliCfgFile)
}

def apiGet(
Expand Down Expand Up @@ -906,4 +907,40 @@ class ApiGwTests
var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
}
}

it should "list api alphabetically by Base/Rel/Verb" in {
val baseName = "/BaseTestPathApiList"
val actionName = "actionName"
val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
try {
// Create Action for apis
var action = wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
println("action creation: " + action.stdout)
// Create apis
for (i <- 1 to 3) {
val base = s"$baseName$i"
var api = apiCreate(
basepath = Some(base),
relpath = Some("/relPath"),
operation = Some("GET"),
action = Some(actionName))
println("api creation: " + api.stdout)
}
val original = apiList(nameSort = Some(true)).stdout
val originalFull = apiList(full = Some(true), nameSort = Some(true)).stdout
val scalaSorted = List(s"${baseName}1" + "/", s"${baseName}2" + "/", s"${baseName}3" + "/")
val regex = s"${baseName}[1-3]/".r
val list = (regex.findAllMatchIn(original)).toList
val listFull = (regex.findAllMatchIn(originalFull)).toList

scalaSorted.toString shouldEqual list.toString
scalaSorted.toString shouldEqual listFull.toString
} finally {
// Clean up Apis
for (i <- 1 to 3) {
val deleteApis = apiDelete(basepathOrApiName = s"${baseName}$i", expectedExitCode = DONTCARE_EXIT)
}
val deleteAction = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
}
}
}
135 changes: 135 additions & 0 deletions tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,141 @@ class WskBasicUsageTests
}, 5, Some(1 second))
}

it should "return a list of alphabetized actions" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
// Declare 4 actions, create them out of alphabetical order
val actionName = "actionAlphaTest"
for (i <- 1 to 3) {
val name = s"$actionName$i"
assetHelper.withCleaner(wsk.action, name) {
(action, name) =>
action.create(name, defaultAction)
}
}
retry({
val original = wsk.action.list(nameSort = Some(true)).stdout
// Create list with action names in correct order
val scalaSorted = List(s"${actionName}1", s"${actionName}2", s"${actionName}3")
// Filter out everything not previously created
val regex = s"${actionName}[1-3]".r
// Retrieve action names into list as found in original
val list = (regex.findAllMatchIn(original)).toList
scalaSorted.toString shouldEqual list.toString
}, 5, Some(1 second))
}

it should "return an alphabetized list with default package actions on top" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
// Declare 4 actions, create them out of alphabetical order
val actionName = "actionPackageAlphaTest"
val packageName = "packageAlphaTest"
assetHelper.withCleaner(wsk.action, actionName) {
(action, actionName) =>
action.create(actionName, defaultAction)
}
assetHelper.withCleaner(wsk.pkg, packageName) {
(pkg, packageName) =>
pkg.create(packageName)
}
for (i <- 1 to 3) {
val name = s"${packageName}/${actionName}$i"
assetHelper.withCleaner(wsk.action, name) {
(action, name) =>
action.create(name, defaultAction)
}
}
retry({
val original = wsk.action.list(nameSort = Some(true)).stdout
// Create list with action names in correct order
val scalaSorted = List(s"$actionName", s"${packageName}/${actionName}1", s"${packageName}/${actionName}2", s"${packageName}/${actionName}3")
// Filter out everything not previously created
val regexNoPackage = s"$actionName".r
val regexWithPackage = s"${packageName}/${actionName}[1-3]".r
// Retrieve action names into list as found in original
val list = regexNoPackage.findFirstIn(original).get :: (regexWithPackage.findAllMatchIn(original)).toList
scalaSorted.toString shouldEqual list.toString
}, 5, Some(1 second))
}

it should "return a list of alphabetized packages" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
// Declare 3 packages, create them out of alphabetical order
val packageName = "pkgAlphaTest"
for (i <- 1 to 3) {
val name = s"$packageName$i"
assetHelper.withCleaner(wsk.pkg, name) {
(pkg, name) =>
pkg.create(name)
}
}
retry({
val original = wsk.pkg.list(nameSort = Some(true)).stdout
// Create list with package names in correct order
val scalaSorted = List(s"${packageName}1", s"${packageName}2", s"${packageName}3")
// Filter out everything not previously created
val regex = s"${packageName}[1-3]".r
// Retrieve package names into list as found in original
val list = (regex.findAllMatchIn(original)).toList
scalaSorted.toString shouldEqual list.toString
}, 5, Some(1 second))
}

it should "return a list of alphabetized triggers" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
// Declare 4 triggers, create them out of alphabetical order
val triggerName = "triggerAlphaTest"
for (i <- 1 to 3) {
val name = s"$triggerName$i"
assetHelper.withCleaner(wsk.trigger, name) {
(trigger, name) =>
trigger.create(name)
}
}
retry({
val original = wsk.trigger.list(nameSort = Some(true)).stdout
// Create list with trigger names in correct order
val scalaSorted = List(s"${triggerName}1", s"${triggerName}2", s"${triggerName}3")
// Filter out everything not previously created
val regex = s"${triggerName}[1-3]".r
// Retrieve trigger names into list as found in original
val list = (regex.findAllMatchIn(original)).toList
scalaSorted.toString shouldEqual list.toString
}, 5, Some(1 second))
}

it should "return a list of alphabetized rules" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
// Declare a trigger and an action for the purposes of creating rules
val triggerName = "listRulesTrigger"
val actionName = "listRulesAction"

assetHelper.withCleaner(wsk.trigger, triggerName) {
(trigger, name) => trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) {
(action, name) => action.create(name, defaultAction)
}
// Declare 3 rules, create them out of alphabetical order
val ruleName = "ruleAlphaTest"
for (i <- 1 to 3) {
val name = s"$ruleName$i"
assetHelper.withCleaner(wsk.rule, name) {
(rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
}
retry({
val original = wsk.rule.list(nameSort = Some(true)).stdout
// Create list with rule names in correct order
val scalaSorted = List(s"${ruleName}1", s"${ruleName}2", s"${ruleName}3")
// Filter out everything not previously created
val regex = s"${ruleName}[1-3]".r
// Retrieve rule names into list as found in original
val list = (regex.findAllMatchIn(original)).toList
scalaSorted.toString shouldEqual list.toString
})
}

behavior of "Wsk params and annotations"

it should "reject commands that are executed with invalid JSON for annotations and parameters" in {
Expand Down
Loading

0 comments on commit fe42018

Please sign in to comment.