Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alphabetize listings in CLI #2326

Merged
merged 1 commit into from
Aug 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions docs/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1022,13 +1022,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 @@ -959,6 +959,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
Copy link
Member

@rabbah rabbah Aug 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm guessing .toString here isn't necessary.

is it?
applies in multiple places.

}, 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