blob: 2393a5169037ee3a05001dc0c84451dfdf25fc38 [file] [log] [blame]
/*
* Copyright 2015-2016 IBM Corporation
*
* Licensed 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 commands
import (
"encoding/base64"
"errors"
"fmt"
"path/filepath"
"io"
"strings"
"github.com/openwhisk/openwhisk-client-go/whisk"
"github.com/openwhisk/openwhisk-cli/wski18n"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/mattn/go-colorable"
)
const MEMORY_LIMIT = 256
const TIMEOUT_LIMIT = 60000
const LOGSIZE_LIMIT = 10
const ACTIVATION_ID = "activationId"
const WEB_EXPORT_ANNOT = "web-export"
const RAW_HTTP_ANNOT = "raw-http"
const FINAL_ANNOT = "final"
var actionCmd = &cobra.Command{
Use: "action",
Short: wski18n.T("work with actions"),
}
var actionCreateCmd = &cobra.Command{
Use: "create ACTION_NAME ACTION",
Short: wski18n.T("create a new action"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var action *whisk.Action
var err error
if whiskErr := checkArgs(
args,
2,
2,
"Action create",
wski18n.T("An action name and action are required.")); whiskErr != nil {
return whiskErr
}
if action, err = parseAction(cmd, args, false); err != nil {
return actionParseError(cmd, args, err)
}
if _, _, err = client.Actions.Insert(action, false); err != nil {
return actionInsertError(action, err)
}
printActionCreated(action.Name)
return nil
},
}
var actionUpdateCmd = &cobra.Command{
Use: "update ACTION_NAME [ACTION]",
Short: wski18n.T("update an existing action, or create an action if it does not exist"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var action *whisk.Action
var err error
if whiskErr := checkArgs(
args,
1,
2,
"Action update",
wski18n.T("An action name is required. An action is optional.")); whiskErr != nil {
return whiskErr
}
if action, err = parseAction(cmd, args, false); err != nil {
return actionParseError(cmd, args, err)
}
if _, _, err = client.Actions.Insert(action, true); err != nil {
return actionInsertError(action, err)
}
printActionUpdated(action.Name)
return nil
},
}
var actionInvokeCmd = &cobra.Command{
Use: "invoke ACTION_NAME",
Short: wski18n.T("invoke action"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var err error
var parameters interface{}
var qualifiedName QualifiedName
var paramArgs []string
if whiskErr := checkArgs(
args,
1,
1,
"Action invoke",
wski18n.T("An action name is required.")); whiskErr != nil {
return whiskErr
}
if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
return parseQualifiedNameError(args[0], err)
}
client.Namespace = qualifiedName.namespace
paramArgs = flags.common.param
if len(paramArgs) > 0 {
if parameters, err = getJSONFromStrings(paramArgs, false); err != nil {
return getJSONFromStringsParamError(paramArgs, false, err)
}
}
if flags.action.result {flags.common.blocking = true}
res, _, err := client.Actions.Invoke(
qualifiedName.entityName,
parameters,
flags.common.blocking,
flags.action.result)
return handleInvocationResponse(qualifiedName, parameters, res, err)
},
}
func handleInvocationResponse(
qualifiedName QualifiedName,
parameters interface{},
result map[string]interface{},
err error) (error) {
if err == nil {
printInvocationMsg(
qualifiedName.namespace,
qualifiedName.entityName,
getValueFromJSONResponse(ACTIVATION_ID, result),
result,
color.Output)
} else {
if !flags.common.blocking {
return handleInvocationError(err, qualifiedName.entityName, parameters)
} else {
if isBlockingTimeout(err) {
printBlockingTimeoutMsg(
qualifiedName.namespace,
qualifiedName.entityName,
getValueFromJSONResponse(ACTIVATION_ID, result))
} else if isApplicationError(err) {
printInvocationMsg(
qualifiedName.namespace,
qualifiedName.entityName,
getValueFromJSONResponse(ACTIVATION_ID, result),
result,
colorable.NewColorableStderr())
} else {
return handleInvocationError(err, qualifiedName.entityName, parameters)
}
}
}
return err
}
var actionGetCmd = &cobra.Command{
Use: "get ACTION_NAME [FIELD_FILTER]",
Short: wski18n.T("get action"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var err error
var field string
var action *whisk.Action
var qualifiedName QualifiedName
if whiskErr := checkArgs(args, 1, 2, "Action get", wski18n.T("An action name is required.")); whiskErr != nil {
return whiskErr
}
if len(args) > 1 {
field = args[1]
if !fieldExists(&whisk.Action{}, field) {
return invalidFieldFilterError(field)
}
}
if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
return parseQualifiedNameError(args[0], err)
}
client.Namespace = qualifiedName.namespace
if action, _, err = client.Actions.Get(qualifiedName.entityName); err != nil {
return actionGetError(qualifiedName.entityName, err)
}
if flags.common.summary {
printSummary(action)
} else {
if len(field) > 0 {
printActionGetWithField(qualifiedName.entityName, field, action)
} else {
printActionGet(qualifiedName.entityName, action)
}
}
return nil
},
}
var actionDeleteCmd = &cobra.Command{
Use: "delete ACTION_NAME",
Short: wski18n.T("delete action"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var qualifiedName QualifiedName
var err error
if whiskErr := checkArgs(
args,
1,
1,
"Action delete",
wski18n.T("An action name is required.")); whiskErr != nil {
return whiskErr
}
if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
return parseQualifiedNameError(args[0], err)
}
client.Namespace = qualifiedName.namespace
if _, err = client.Actions.Delete(qualifiedName.entityName); err != nil {
return actionDeleteError(qualifiedName.entityName, err)
}
printActionDeleted(qualifiedName.entityName)
return nil
},
}
var actionListCmd = &cobra.Command{
Use: "list [NAMESPACE]",
Short: wski18n.T("list all actions"),
SilenceUsage: true,
SilenceErrors: true,
PreRunE: setupClientConfig,
RunE: func(cmd *cobra.Command, args []string) error {
var qualifiedName QualifiedName
var actions []whisk.Action
var err error
if len(args) == 1 {
if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
return parseQualifiedNameError(args[0], err)
}
client.Namespace = qualifiedName.namespace
} else if whiskErr := checkArgs(
args,
0,
1,
"Action list",
wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil {
return whiskErr
}
options := &whisk.ActionListOptions{
Skip: flags.common.skip,
Limit: flags.common.limit,
}
if actions, _, err = client.Actions.List(qualifiedName.entityName, options); err != nil {
return actionListError(qualifiedName.entityName, options, err)
}
printList(actions)
return nil
},
}
func parseAction(cmd *cobra.Command, args []string, update bool) (*whisk.Action, error) {
var err error
var artifact string
var existingAction *whisk.Action
var paramArgs []string
var annotArgs []string
var parameters interface{}
var annotations interface{}
qualifiedName := QualifiedName{}
if qualifiedName, err = parseQualifiedName(args[0]); err != nil {
return nil, parseQualifiedNameError(args[0], err)
}
if len(args) == 2 {
artifact = args[1]
}
client.Namespace = qualifiedName.namespace
action := new(whisk.Action)
action.Name = qualifiedName.entityName
action.Namespace = qualifiedName.namespace
action.Limits = getLimits(
cmd.LocalFlags().Changed(MEMORY_FLAG),
cmd.LocalFlags().Changed(LOG_SIZE_FLAG),
cmd.LocalFlags().Changed(TIMEOUT_FLAG),
flags.action.memory,
flags.action.logsize,
flags.action.timeout)
paramArgs = flags.common.param
annotArgs = flags.common.annotation
if len(paramArgs) > 0 {
if parameters, err = getJSONFromStrings(paramArgs, true); err != nil {
return nil, getJSONFromStringsParamError(paramArgs, true, err)
}
action.Parameters = parameters.(whisk.KeyValueArr)
}
if len(annotArgs) > 0 {
if annotations, err = getJSONFromStrings(annotArgs, true); err != nil {
return nil, getJSONFromStringsAnnotError(annotArgs, true, err)
}
action.Annotations = annotations.(whisk.KeyValueArr)
}
if flags.action.copy {
copiedQualifiedName := QualifiedName{}
if copiedQualifiedName, err = parseQualifiedName(args[1]); err != nil {
return nil, parseQualifiedNameError(args[1], err)
}
client.Namespace = copiedQualifiedName.namespace
if existingAction, _, err = client.Actions.Get(copiedQualifiedName.entityName); err != nil {
return nil, actionGetError(copiedQualifiedName.entityName, err)
}
client.Namespace = qualifiedName.namespace
action.Exec = existingAction.Exec
action.Parameters = append(action.Parameters, existingAction.Parameters...)
action.Annotations = append(action.Annotations, existingAction.Annotations...)
} else if flags.action.sequence {
action.Exec = new(whisk.Exec)
action.Exec.Kind = "sequence"
action.Exec.Components = csvToQualifiedActions(artifact)
} else if len(artifact) > 0 {
action.Exec, err = getExec(args[1], flags.action.kind, flags.action.docker, flags.action.main)
}
if cmd.LocalFlags().Changed(WEB_FLAG) {
action.Annotations, err = webAction(flags.action.web, action.Annotations, qualifiedName.entityName, update)
}
whisk.Debug(whisk.DbgInfo, "Parsed action struct: %#v\n", action)
return action, err
}
func getExec(artifact string, kind string, isDocker bool, mainEntry string) (*whisk.Exec, error) {
var err error
var code string
var exec *whisk.Exec
ext := filepath.Ext(artifact)
exec = new(whisk.Exec)
if !isDocker || ext == ".zip" {
code, err = readFile(artifact)
if ext == ".zip" || ext == ".jar" {
// Base64 encode the file
code = base64.StdEncoding.EncodeToString([]byte(code))
exec.Code = &code
} else {
exec.Code = &code
}
if err != nil {
whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", artifact, err)
return nil, err
}
}
if len(kind) > 0 {
exec.Kind = kind
} else if isDocker {
exec.Kind = "blackbox"
if ext != ".zip" {
exec.Image = artifact
} else {
exec.Image = "openwhisk/dockerskeleton"
}
} else if ext == ".swift" {
exec.Kind = "swift:default"
} else if ext == ".js" {
exec.Kind = "nodejs:default"
} else if ext == ".py" {
exec.Kind = "python:default"
} else if ext == ".jar" {
exec.Kind = "java:default"
} else {
if ext == ".zip" {
return nil, zipKindError()
} else {
return nil, extensionError(ext)
}
}
// Error if entry point is not specified for Java
if len(mainEntry) != 0 {
exec.Main = mainEntry
} else {
if exec.Kind == "java" {
return nil, javaEntryError()
}
}
return exec, nil
}
func webAction(webMode string, annotations whisk.KeyValueArr, entityName string, fetch bool) (whisk.KeyValueArr, error){
switch strings.ToLower(webMode) {
case "yes":
fallthrough
case "true":
return webActionAnnotations(fetch, annotations, entityName, addWebAnnotations)
case "no":
fallthrough
case "false":
return webActionAnnotations(fetch, annotations, entityName, deleteWebAnnotations)
case "raw":
return webActionAnnotations(fetch, annotations, entityName, addRawAnnotations)
default:
return nil, webInputError(webMode)
}
}
type WebActionAnnotationMethod func(annotations whisk.KeyValueArr) (whisk.KeyValueArr)
func webActionAnnotations(
fetchAnnotations bool,
annotations whisk.KeyValueArr,
entityName string,
webActionAnnotationMethod WebActionAnnotationMethod) (whisk.KeyValueArr, error) {
var action *whisk.Action
var err error
if annotations != nil || !fetchAnnotations {
annotations = webActionAnnotationMethod(annotations)
} else {
if action, _, err = client.Actions.Get(entityName); err != nil {
return nil, actionGetError(entityName, err)
} else {
annotations = webActionAnnotationMethod(action.Annotations)
}
}
return annotations, nil
}
func addWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
annotations = deleteWebAnnotationKeys(annotations)
annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
annotations = addKeyValue(FINAL_ANNOT, true, annotations)
return annotations
}
func deleteWebAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
annotations = deleteWebAnnotationKeys(annotations)
annotations = addKeyValue(WEB_EXPORT_ANNOT, false, annotations)
annotations = addKeyValue(RAW_HTTP_ANNOT, false, annotations)
annotations = addKeyValue(FINAL_ANNOT, false, annotations)
return annotations
}
func addRawAnnotations(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
annotations = deleteWebAnnotationKeys(annotations)
annotations = addKeyValue(WEB_EXPORT_ANNOT, true, annotations)
annotations = addKeyValue(RAW_HTTP_ANNOT, true, annotations)
annotations = addKeyValue(FINAL_ANNOT, true, annotations)
return annotations
}
func deleteWebAnnotationKeys(annotations whisk.KeyValueArr) (whisk.KeyValueArr) {
annotations = deleteKey(WEB_EXPORT_ANNOT, annotations)
annotations = deleteKey(RAW_HTTP_ANNOT, annotations)
annotations = deleteKey(FINAL_ANNOT, annotations)
return annotations
}
func getLimits(memorySet bool, logSizeSet bool, timeoutSet bool, memory int, logSize int, timeout int) (*whisk.Limits) {
var limits *whisk.Limits
if memorySet || logSizeSet || timeoutSet {
limits = new(whisk.Limits)
if memorySet {
limits.Memory = &memory
}
if logSizeSet {
limits.Logsize = &logSize
}
if timeoutSet {
limits.Timeout = &timeout
}
}
return limits
}
func nestedError(errorMessage string, err error) (error) {
return whisk.MakeWskErrorFromWskError(
errors.New(errorMessage),
err,
whisk.EXITCODE_ERR_GENERAL,
whisk.DISPLAY_MSG,
whisk.DISPLAY_USAGE)
}
func nonNestedError(errorMessage string) (error) {
return whisk.MakeWskError(
errors.New(errorMessage),
whisk.EXITCODE_ERR_GENERAL,
whisk.DISPLAY_MSG,
whisk.DISPLAY_USAGE)
}
func actionParseError(cmd *cobra.Command, args []string, err error) (error) {
whisk.Debug(whisk.DbgError, "parseAction(%s, %s) error: %s\n", cmd, args, err)
errMsg := wski18n.T(
"Unable to parse action command arguments: {{.err}}",
map[string]interface{}{
"err": err,
})
return nestedError(errMsg, err)
}
func actionInsertError(action *whisk.Action, err error) (error) {
whisk.Debug(whisk.DbgError, "client.Actions.Insert(%#v, false) error: %s\n", action, err)
errMsg := wski18n.T(
"Unable to create action '{{.name}}': {{.err}}",
map[string]interface{}{
"name": action.Name,
"err": err,
})
return nestedError(errMsg, err)
}
func parseQualifiedNameError(entityName string, err error) (error) {
whisk.Debug(whisk.DbgError, "parseQualifiedName(%s) failed: %s\n", entityName, err)
errMsg := wski18n.T(
"'{{.name}}' is not a valid qualified name: {{.err}}",
map[string]interface{}{
"name": entityName,
"err": err,
})
return nestedError(errMsg, err)
}
func getJSONFromStringsParamError(params []string, keyValueFormat bool, err error) (error) {
whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", params, keyValueFormat, err)
errMsg := wski18n.T(
"Invalid parameter argument '{{.param}}': {{.err}}",
map[string]interface{}{
"param": fmt.Sprintf("%#v", params),
"err": err,
})
return nestedError(errMsg, err)
}
func getJSONFromStringsAnnotError(annots []string, keyValueFormat bool, err error) (error) {
whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %t) failed: %s\n", annots, keyValueFormat, err)
errMsg := wski18n.T(
"Invalid annotation argument '{{.annotation}}': {{.err}}",
map[string]interface{}{
"annotation": fmt.Sprintf("%#v", annots),
"err": err,
})
return nestedError(errMsg, err)
}
func invalidFieldFilterError(field string) (error) {
errMsg := wski18n.T(
"Invalid field filter '{{.arg}}'.",
map[string]interface{}{
"arg": field,
})
return nonNestedError(errMsg)
}
func actionDeleteError(entityName string, err error) (error) {
whisk.Debug(whisk.DbgError, "client.Actions.Delete(%s) error: %s\n", entityName, err)
errMsg := wski18n.T(
"Unable to delete action '{{.name}}': {{.err}}",
map[string]interface{}{
"name": entityName,
"err": err,
})
return nestedError(errMsg, err)
}
func actionGetError(entityName string, err error) (error) {
whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", entityName, err)
errMsg := wski18n.T(
"Unable to get action '{{.name}}': {{.err}}",
map[string]interface{}{
"name": entityName,
"err": err,
})
return nestedError(errMsg, err)
}
func handleInvocationError(err error, entityName string, parameters interface{}) (error) {
whisk.Debug(
whisk.DbgError,
"client.Actions.Invoke(%s, %s, %t) error: %s\n",
entityName, parameters,
flags.common.blocking,
err)
errMsg := wski18n.T(
"Unable to invoke action '{{.name}}': {{.err}}",
map[string]interface{}{
"name": entityName,
"err": err,
})
return nestedError(errMsg, err)
}
func actionListError(entityName string, options *whisk.ActionListOptions, err error) (error) {
whisk.Debug(whisk.DbgError, "client.Actions.List(%s, %#v) error: %s\n", entityName, options, err)
errMsg := wski18n.T(
"Unable to obtain the list of actions for namespace '{{.name}}': {{.err}}",
map[string]interface{}{
"name": getClientNamespace(),
"err": err,
})
return nestedError(errMsg, err)
}
func webInputError(arg string) (error) {
errMsg := wski18n.T(
"Invalid argument '{{.arg}}' for --web flag. Valid input consist of 'yes', 'true', 'raw', 'false', or 'no'.",
map[string]interface{}{
"arg": arg,
})
return nonNestedError(errMsg)
}
func zipKindError() (error) {
errMsg := wski18n.T("creating an action from a .zip artifact requires specifying the action kind explicitly")
return nonNestedError(errMsg)
}
func extensionError(extension string) (error) {
errMsg := wski18n.T(
"'{{.name}}' is not a supported action runtime",
map[string]interface{}{
"name": extension,
})
return nonNestedError(errMsg)
}
func javaEntryError() (error) {
errMsg := wski18n.T("Java actions require --main to specify the fully-qualified name of the main class")
return nonNestedError(errMsg)
}
func printActionCreated(entityName string) {
fmt.Fprintf(
color.Output,
wski18n.T(
"{{.ok}} created action {{.name}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"name": boldString(entityName),
}))
}
func printActionUpdated(entityName string) {
fmt.Fprintf(
color.Output,
wski18n.T(
"{{.ok}} updated action {{.name}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"name": boldString(entityName),
}))
}
func printBlockingTimeoutMsg(namespace string, entityName string, activationID interface{}) {
fmt.Fprintf(
colorable.NewColorableStderr(),
wski18n.T(
"{{.ok}} invoked /{{.namespace}}/{{.name}}, but the request has not yet finished, with id {{.id}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"namespace": boldString(namespace),
"name": boldString(entityName),
"id": boldString(activationID),
}))
}
func printInvocationMsg(
namespace string,
entityName string,
activationID interface{},
response map[string]interface{},
outputStream io.Writer) {
if !flags.action.result {
fmt.Fprintf(
outputStream,
wski18n.T(
"{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"namespace": boldString(namespace),
"name": boldString(entityName),
"id": boldString(activationID),
}))
}
if flags.common.blocking {
printJSON(response, outputStream)
}
}
func printActionGetWithField(entityName string, field string, action *whisk.Action) {
fmt.Fprintf(
color.Output,
wski18n.T(
"{{.ok}} got action {{.name}}, displaying field {{.field}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"name": boldString(entityName),
"field": boldString(field),
}))
printField(action, field)
}
func printActionGet(entityName string, action *whisk.Action) {
fmt.Fprintf(
color.Output,
wski18n.T("{{.ok}} got action {{.name}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"name": boldString(entityName),
}))
printJSON(action)
}
func printActionDeleted(entityName string) {
fmt.Fprintf(
color.Output,
wski18n.T(
"{{.ok}} deleted action {{.name}}\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
"name": boldString(entityName),
}))
}
// Check if the specified action is a web-action
func isWebAction(client *whisk.Client, qname QualifiedName) error {
var err error = nil
savedNs := client.Namespace
client.Namespace = qname.namespace
fullActionName := "/" + qname.namespace + "/" + qname.entityName
action, _, err := client.Actions.Get(qname.entityName)
if err != nil {
whisk.Debug(whisk.DbgError, "client.Actions.Get(%s) error: %s\n", fullActionName, err)
whisk.Debug(whisk.DbgError, "Unable to obtain action '%s' for web action validation\n", fullActionName)
err = errors.New(wski18n.T("API action does not exist"))
} else {
err = errors.New(wski18n.T("API action '{{.name}}' is not a web action. Issue 'wsk action update {{.name}} --web true' to convert the action to a web action.",
map[string]interface{}{"name": fullActionName}))
weVal := getValue(action.Annotations, "web-export")
if (weVal == nil) {
whisk.Debug(whisk.DbgError, "getValue(annotations, web-export) for action %s found no value\n", fullActionName)
} else {
var webExport bool
var ok bool
if webExport, ok = weVal.(bool); !ok {
whisk.Debug(whisk.DbgError, "web-export annotation value (%v) is not a boolean\n", weVal)
} else if !webExport {
whisk.Debug(whisk.DbgError, "web-export annotation value is false\n", weVal)
} else {
err = nil
}
}
}
client.Namespace = savedNs
return err
}
func init() {
actionCreateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
actionCreateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
actionCreateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
actionCreateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
actionCreateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
actionCreateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
actionCreateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
actionCreateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
actionCreateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", nil, wski18n.T("annotation values in `KEY VALUE` format"))
actionCreateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
actionCreateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", nil, wski18n.T("parameter values in `KEY VALUE` format"))
actionCreateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
actionCreateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
actionUpdateCmd.Flags().BoolVar(&flags.action.docker, "docker", false, wski18n.T("treat ACTION as docker image path on dockerhub"))
actionUpdateCmd.Flags().BoolVar(&flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
actionUpdateCmd.Flags().BoolVar(&flags.action.sequence, "sequence", false, wski18n.T("treat ACTION as comma separated sequence of actions to invoke"))
actionUpdateCmd.Flags().StringVar(&flags.action.kind, "kind", "", wski18n.T("the `KIND` of the action runtime (example: swift:default, nodejs:default)"))
actionUpdateCmd.Flags().StringVar(&flags.action.main, "main", "", wski18n.T("the name of the action entry point (function or fully-qualified method name when applicable)"))
actionUpdateCmd.Flags().IntVarP(&flags.action.timeout, "timeout", "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated"))
actionUpdateCmd.Flags().IntVarP(&flags.action.memory, "memory", "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action"))
actionUpdateCmd.Flags().IntVarP(&flags.action.logsize, "logsize", "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action"))
actionUpdateCmd.Flags().StringSliceVarP(&flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
actionUpdateCmd.Flags().StringVarP(&flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
actionUpdateCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
actionUpdateCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
actionUpdateCmd.Flags().StringVar(&flags.action.web, "web", "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
actionInvokeCmd.Flags().StringSliceVarP(&flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
actionInvokeCmd.Flags().StringVarP(&flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
actionInvokeCmd.Flags().BoolVarP(&flags.common.blocking, "blocking", "b", false, wski18n.T("blocking invoke"))
actionInvokeCmd.Flags().BoolVarP(&flags.action.result, "result", "r", false, wski18n.T("blocking invoke; show only activation result (unless there is a failure)"))
actionGetCmd.Flags().BoolVarP(&flags.common.summary, "summary", "s", false, wski18n.T("summarize action details"))
actionListCmd.Flags().IntVarP(&flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
actionListCmd.Flags().IntVarP(&flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
actionCmd.AddCommand(
actionCreateCmd,
actionUpdateCmd,
actionInvokeCmd,
actionGetCmd,
actionDeleteCmd,
actionListCmd,
)
}