| /* |
| * 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 commands |
| |
| import ( |
| "encoding/base64" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "math/rand" |
| "os" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "time" |
| |
| "github.com/apache/openwhisk-cli/wski18n" |
| "github.com/apache/openwhisk-client-go/whisk" |
| |
| "github.com/fatih/color" |
| "github.com/mattn/go-colorable" |
| "github.com/spf13/cobra" |
| ) |
| |
| const ( |
| MEMORY_LIMIT = 256 |
| TIMEOUT_LIMIT = 60000 |
| LOGSIZE_LIMIT = 10 |
| CONCURRENCY_LIMIT = 1 |
| ACTIVATION_ID = "activationId" |
| WEB_EXPORT_ANNOT = "web-export" |
| RAW_HTTP_ANNOT = "raw-http" |
| FINAL_ANNOT = "final" |
| WEB_SECURE_ANNOT = "require-whisk-auth" |
| NODE_JS_EXT = ".js" |
| PYTHON_EXT = ".py" |
| JAVA_EXT = ".jar" |
| SWIFT_EXT = ".swift" |
| ZIP_EXT = ".zip" |
| PHP_EXT = ".php" |
| BAL_BIN_EXT = ".balx" |
| RUBY_EXT = ".rb" |
| GO_EXT = ".go" |
| NODE_JS = "nodejs" |
| PYTHON = "python" |
| JAVA = "java" |
| SWIFT = "swift" |
| PHP = "php" |
| RUBY = "ruby" |
| GO = "go" |
| BAL = "ballerina" |
| DEFAULT = "default" |
| BLACKBOX = "blackbox" |
| SEQUENCE = "sequence" |
| FETCH_CODE = true |
| DO_NOT_FETCH_CODE = false |
| ACTION_UPDATE = true |
| ACTION_CREATE = false |
| MAX_JS_INT = 1<<53 - 1 |
| ) |
| |
| 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, |
| 1, |
| 2, |
| "Action create", |
| wski18n.T("An action name and code artifact are required.")); whiskErr != nil { |
| return whiskErr |
| } |
| |
| if action, err = parseAction(cmd, args, false); err != nil { |
| return actionParseError(cmd, args, err) |
| } |
| |
| if action, err = augmentAction(cmd, args, action, ACTION_CREATE); 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. A code artifact is optional.")); whiskErr != nil { |
| return whiskErr |
| } |
| |
| if action, err = parseAction(cmd, args, true); err != nil { |
| return actionParseError(cmd, args, err) |
| } |
| |
| if action, err = augmentAction(cmd, args, action, ACTION_UPDATE); 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 qualifiedName = new(QualifiedName) |
| var parameters interface{} |
| |
| if whiskErr := CheckArgs( |
| args, |
| 1, |
| 1, |
| "Action invoke", |
| wski18n.T("An action name is required.")); whiskErr != nil { |
| return whiskErr |
| } |
| |
| if qualifiedName, err = NewQualifiedName(args[0]); err != nil { |
| return NewQualifiedNameError(args[0], err) |
| } |
| |
| parameters = getParameters(Flags.common.param, false, false) |
| blocking := Flags.common.blocking || Flags.action.result |
| resultOnly := Flags.action.result |
| header := !resultOnly |
| |
| res, err := invokeAction( |
| *qualifiedName, |
| parameters, |
| blocking, |
| resultOnly) |
| |
| return printInvocationResponse(*qualifiedName, blocking, header, res, err) |
| }, |
| } |
| |
| func invokeAction( |
| qualifiedName QualifiedName, |
| parameters interface{}, |
| blocking bool, |
| result bool) (map[string]interface{}, error) { |
| // TODO remove all global modifiers |
| Client.Namespace = qualifiedName.GetNamespace() |
| res, _, err := Client.Actions.Invoke( |
| qualifiedName.GetEntityName(), |
| parameters, |
| blocking, |
| result) |
| return res, err |
| } |
| |
| func printInvocationResponse( |
| qualifiedName QualifiedName, |
| blocking bool, |
| header bool, |
| result map[string]interface{}, |
| err error) error { |
| if err == nil { |
| printInvocationMsg(qualifiedName, blocking, header, result, color.Output) |
| } else { |
| if !blocking { |
| return handleInvocationError(err, qualifiedName.GetEntityName()) |
| } else { |
| return printFailedBlockingInvocationResponse(qualifiedName, header, result, err) |
| } |
| } |
| |
| return err |
| } |
| |
| func printFailedBlockingInvocationResponse( |
| qualifiedName QualifiedName, |
| header bool, |
| result map[string]interface{}, |
| err error) error { |
| if isBlockingTimeout(err) { |
| printBlockingTimeoutMsg( |
| qualifiedName.GetNamespace(), |
| qualifiedName.GetEntityName(), |
| getValueFromJSONResponse(ACTIVATION_ID, result)) |
| return err |
| } else if isApplicationError(err) { |
| printInvocationMsg( |
| qualifiedName, |
| true, |
| header, |
| result, |
| colorable.NewColorableStderr()) |
| return err |
| } else { |
| return handleInvocationError(err, qualifiedName.GetEntityName()) |
| } |
| } |
| |
| var actionGetCmd = &cobra.Command{ |
| Use: "get ACTION_NAME [FIELD_FILTER | --summary | --url]", |
| 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 = new(QualifiedName) |
| var fetchCode bool |
| |
| if whiskErr := CheckArgs(args, 1, 2, "Action get", wski18n.T("An action name is required.")); whiskErr != nil { |
| return whiskErr |
| } |
| |
| if !Flags.action.url && !Flags.common.summary && len(args) > 1 { |
| field = args[1] |
| |
| if !fieldExists(&whisk.Action{}, field) { |
| return invalidFieldFilterError(field) |
| } |
| } |
| |
| if qualifiedName, err = NewQualifiedName(args[0]); err != nil { |
| return NewQualifiedNameError(args[0], err) |
| } |
| |
| Client.Namespace = qualifiedName.GetNamespace() |
| fetchCode = cmd.LocalFlags().Changed(SAVE_AS_FLAG) || cmd.LocalFlags().Changed(SAVE_FLAG) |
| |
| if action, _, err = Client.Actions.Get(qualifiedName.GetEntityName(), fetchCode); err != nil { |
| return actionGetError(qualifiedName.GetEntityName(), fetchCode, err) |
| } |
| |
| if Flags.action.url { |
| actionURL, err := action.ActionURL(Properties.APIHost, |
| DefaultOpenWhiskApiPath, |
| Properties.APIVersion, |
| qualifiedName.GetPackageName()) |
| if err != nil { |
| errStr := wski18n.T("Invalid host address '{{.host}}': {{.err}}", |
| map[string]interface{}{"host": Properties.APIHost, "err": err}) |
| werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) |
| return werr |
| } |
| printActionGetWithURL(qualifiedName.GetEntity(), actionURL) |
| } else if Flags.common.summary { |
| printSummary(action) |
| } else if cmd.LocalFlags().Changed(SAVE_AS_FLAG) || cmd.LocalFlags().Changed(SAVE_FLAG) { |
| return saveCode(*action, Flags.action.saveAs) |
| } else { |
| if len(field) > 0 { |
| printActionGetWithField(qualifiedName.GetEntityName(), field, action) |
| } else { |
| printActionGet(qualifiedName.GetEntityName(), 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 = new(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 = NewQualifiedName(args[0]); err != nil { |
| return NewQualifiedNameError(args[0], err) |
| } |
| |
| Client.Namespace = qualifiedName.GetNamespace() |
| |
| if _, err = Client.Actions.Delete(qualifiedName.GetEntityName()); err != nil { |
| return actionDeleteError(qualifiedName.GetEntityName(), err) |
| } |
| |
| printActionDeleted(qualifiedName.GetEntityName()) |
| |
| return nil |
| }, |
| } |
| |
| var actionListCmd = &cobra.Command{ |
| Use: "list [ NAMESPACE | PACKAGE_NAME ]", |
| Short: wski18n.T("list all actions in a namespace or actions contained in a package"), |
| SilenceUsage: true, |
| SilenceErrors: true, |
| PreRunE: SetupClientConfig, |
| RunE: func(cmd *cobra.Command, args []string) error { |
| var qualifiedName = new(QualifiedName) |
| var actions []whisk.Action |
| var err error |
| |
| if whiskErr := CheckArgs( |
| args, |
| 0, |
| 1, |
| "Action list", |
| wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil { |
| return whiskErr |
| } |
| |
| if len(args) == 1 { |
| if qualifiedName, err = NewQualifiedName(args[0]); err != nil { |
| return NewQualifiedNameError(args[0], err) |
| } |
| |
| Client.Namespace = qualifiedName.GetNamespace() |
| } |
| |
| options := &whisk.ActionListOptions{ |
| Skip: Flags.common.skip, |
| Limit: Flags.common.limit, |
| } |
| |
| if actions, _, err = Client.Actions.List(qualifiedName.GetEntityName(), options); err != nil { |
| return actionListError(qualifiedName.GetEntityName(), options, err) |
| } |
| |
| sortByName := Flags.common.nameSort |
| printList(actions, sortByName) |
| |
| return nil |
| }, |
| } |
| |
| func parseAction(cmd *cobra.Command, args []string, update bool) (*whisk.Action, error) { |
| var err error |
| var existingAction *whisk.Action |
| var paramArgs []string |
| var annotArgs []string |
| var parameters interface{} |
| var annotations interface{} |
| |
| var qualifiedName = new(QualifiedName) |
| |
| if qualifiedName, err = NewQualifiedName(args[0]); err != nil { |
| return nil, NewQualifiedNameError(args[0], err) |
| } |
| |
| Client.Namespace = qualifiedName.GetNamespace() |
| action := new(whisk.Action) |
| action.Name = qualifiedName.GetEntityName() |
| action.Namespace = qualifiedName.GetNamespace() |
| action.Limits = getLimits( |
| cmd.LocalFlags().Changed(MEMORY_FLAG), |
| cmd.LocalFlags().Changed(LOG_SIZE_FLAG), |
| cmd.LocalFlags().Changed(TIMEOUT_FLAG), |
| cmd.LocalFlags().Changed(CONCURRENCY_FLAG), |
| Flags.action.memory, |
| Flags.action.logsize, |
| Flags.action.timeout, |
| Flags.action.concurrency) |
| |
| 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 len(Flags.action.kind) > 0 && len(Flags.action.docker) > 0 { |
| errStr := wski18n.T("Cannot specify both --kind and --docker at the same time.") |
| return nil, whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) |
| } |
| |
| if Flags.action.copy { |
| var copiedQualifiedName = new(QualifiedName) |
| |
| if copiedQualifiedName, err = NewQualifiedName(args[1]); err != nil { |
| return nil, NewQualifiedNameError(args[1], err) |
| } |
| |
| Client.Namespace = copiedQualifiedName.GetNamespace() |
| |
| if existingAction, _, err = Client.Actions.Get(copiedQualifiedName.GetEntityName(), FETCH_CODE); err != nil { |
| return nil, actionGetError(copiedQualifiedName.GetEntityName(), FETCH_CODE, err) |
| } |
| |
| Client.Namespace = qualifiedName.GetNamespace() |
| action.Exec = existingAction.Exec |
| action.Parameters = append(action.Parameters, existingAction.Parameters...) |
| action.Annotations = append(action.Annotations, existingAction.Annotations...) |
| } else if Flags.action.sequence { |
| if len(args) == 2 { |
| action.Exec = new(whisk.Exec) |
| action.Exec.Kind = SEQUENCE |
| action.Exec.Components = csvToQualifiedActions(args[1]) |
| } else { |
| return nil, noArtifactError() |
| } |
| } else if len(args) > 1 || len(Flags.action.docker) > 0 { |
| action.Exec, err = getExec(args, Flags.action) |
| if err != nil { |
| return nil, err |
| } |
| } else if !update { |
| return nil, noArtifactError() |
| } |
| |
| whisk.Debug(whisk.DbgInfo, "Parsed action struct: %#v\n", action) |
| return action, err |
| } |
| |
| func augmentAction(cmd *cobra.Command, args []string, action *whisk.Action, update bool) (*whisk.Action, error) { |
| var err error |
| var existingAction *whisk.Action = nil |
| var augmentedAction *whisk.Action = new(whisk.Action) |
| *augmentedAction = *action |
| |
| if update { |
| if existingAction, _, err = Client.Actions.Get(action.Name, DO_NOT_FETCH_CODE); err != nil { |
| whiskErr, isWhiskError := err.(*whisk.WskError) |
| |
| if (isWhiskError && whiskErr.ExitCode != whisk.EXIT_CODE_NOT_FOUND) || !isWhiskError { |
| return nil, actionGetError(action.Name, DO_NOT_FETCH_CODE, err) |
| } |
| } |
| } |
| |
| // Augment the action's annotations with the --web related annotations |
| if augmentedAction, err = augmentWebArg(cmd, args, action, augmentedAction, existingAction); err != nil { |
| return nil, err |
| } |
| |
| // Augment the action's annotations with the --web-secure related annotations |
| if augmentedAction, err = augmentWebSecureArg(cmd, args, action, augmentedAction, existingAction); err != nil { |
| return nil, err |
| } |
| |
| whisk.Debug(whisk.DbgInfo, "Augmented action struct: %#v\n", augmentedAction) |
| return augmentedAction, err |
| } |
| |
| func augmentWebArg(cmd *cobra.Command, args []string, action *whisk.Action, augmentedAction *whisk.Action, existingAction *whisk.Action) (*whisk.Action, error) { |
| var err error |
| preserveAnnotations := action.Annotations == nil |
| |
| if cmd.LocalFlags().Changed(WEB_FLAG) { |
| augmentedAction.Annotations, err = webAction(Flags.action.web, action.Annotations, action.Name, preserveAnnotations, existingAction) |
| if existingAction != nil && err == nil { |
| // Always carry forward any existing --web-secure annotation value |
| // Although it can be overwritten later if --web-secure is set |
| webSecureAnnotations := getWebSecureAnnotations(existingAction) |
| if len(webSecureAnnotations) > 0 { |
| augmentedAction.Annotations = augmentedAction.Annotations.AppendKeyValueArr(webSecureAnnotations) |
| } |
| } |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| whisk.Debug(whisk.DbgInfo, "augmentWebArg: Augmented action struct: %#v\n", augmentedAction) |
| return augmentedAction, nil |
| } |
| |
| /* |
| * Return a whisk.Action augmented with --web-secure annotation updates |
| * originalAction: a action constructed from command line argument |
| * action: an action constructed from command line args + possible other augmentation (i.e. --web annotations) |
| * existingAction: on an action update, this is the existing action |
| */ |
| func augmentWebSecureArg(cmd *cobra.Command, args []string, originalAction *whisk.Action, action *whisk.Action, existingAction *whisk.Action) (*whisk.Action, error) { |
| preserveAnnotations := action.Annotations == nil |
| var augmentedAction *whisk.Action = new(whisk.Action) |
| *augmentedAction = *action |
| disableWebAction := strings.ToLower(Flags.action.web) == "false" || strings.ToLower(Flags.action.web) == "no" |
| isWebSecureFlagValidToUse := action.WebAction() || (existingAction != nil && existingAction.WebAction() && !disableWebAction) |
| |
| // Process the --web-secure flag when set |
| if cmd.LocalFlags().Changed(WEB_SECURE_FLAG) { |
| // The --web-secure option is only valid when: |
| // 1. action --web is set to either true or raw (i.e. web-export annotation is true) |
| // -OR- |
| // 2. existing action web-export annotation is true && action --web is not false/no |
| whisk.Debug(whisk.DbgInfo, "disableWebAction: %v isWebSecureFlagValidToUse: %v\n", disableWebAction, isWebSecureFlagValidToUse) |
| if !isWebSecureFlagValidToUse { |
| return nil, webSecureUsageError() |
| } |
| |
| // Carry forward some or all of the existing action's annotations |
| // all -> if original command line had at least one annotation specified |
| // some -> if original command line had NO annotations, carry forward web/websecure annotation values |
| if existingAction != nil { |
| if preserveAnnotations { |
| augmentedAction.Annotations = action.Annotations.AppendKeyValueArr(existingAction.Annotations) |
| } else { |
| augmentedAction.Annotations = action.Annotations.AppendKeyValueArr(getWebActionAnnotations(existingAction)) |
| augmentedAction.Annotations = augmentedAction.Annotations.AppendKeyValueArr(getWebSecureAnnotations(existingAction)) |
| } |
| } |
| augmentedAction.Annotations = updateWebSecureAnnotation(Flags.action.websecure, augmentedAction.Annotations) |
| } |
| |
| whisk.Debug(whisk.DbgInfo, "augmentWebSecureArg: Augmented action struct: %#v\n", augmentedAction) |
| return augmentedAction, nil |
| } |
| |
| func getExec(args []string, params ActionFlags) (*whisk.Exec, error) { |
| var err error |
| var code string |
| var exec *whisk.Exec |
| |
| exec = new(whisk.Exec) |
| kind := params.kind |
| isNative := params.native |
| docker := params.docker |
| mainEntry := params.main |
| ext := "" |
| |
| if len(args) == 2 { |
| artifact := args[1] |
| ext = filepath.Ext(artifact) |
| code, err = ReadFile(artifact) |
| |
| if err != nil { |
| whisk.Debug(whisk.DbgError, "ReadFile(%s) error: %s\n", artifact, err) |
| return nil, err |
| } |
| |
| if ext == ZIP_EXT || ext == JAVA_EXT || ext == BAL_BIN_EXT { |
| code = base64.StdEncoding.EncodeToString([]byte(code)) |
| } |
| |
| exec.Code = &code |
| } else if len(args) == 1 && len(docker) == 0 { |
| return nil, noArtifactError() |
| } else if len(args) > 1 { |
| return nil, noArtifactError() |
| } |
| |
| if len(kind) > 0 { |
| exec.Kind = kind |
| } else if len(docker) > 0 || isNative { |
| exec.Kind = BLACKBOX |
| if isNative { |
| exec.Image = "openwhisk/dockerskeleton" |
| } else { |
| exec.Image = docker |
| } |
| } else if ext == SWIFT_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", SWIFT, DEFAULT) |
| } else if ext == NODE_JS_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", NODE_JS, DEFAULT) |
| } else if ext == PYTHON_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", PYTHON, DEFAULT) |
| } else if ext == JAVA_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", JAVA, DEFAULT) |
| } else if ext == PHP_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", PHP, DEFAULT) |
| } else if ext == RUBY_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", RUBY, DEFAULT) |
| } else if ext == GO_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", GO, DEFAULT) |
| } else if ext == BAL_BIN_EXT { |
| exec.Kind = fmt.Sprintf("%s:%s", BAL, DEFAULT) |
| } else { |
| if ext == ZIP_EXT { |
| 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 getBinaryKindExtension(runtime string) (extension string) { |
| switch strings.ToLower(runtime) { |
| case JAVA: |
| extension = JAVA_EXT |
| case BAL: |
| extension = BAL_BIN_EXT |
| default: |
| extension = ZIP_EXT |
| } |
| |
| return extension |
| } |
| |
| func getKindExtension(runtime string) (extension string) { |
| switch strings.ToLower(runtime) { |
| case NODE_JS: |
| extension = NODE_JS_EXT |
| case PYTHON: |
| extension = PYTHON_EXT |
| case SWIFT: |
| fallthrough |
| case PHP: |
| extension = fmt.Sprintf(".%s", runtime) |
| case RUBY: |
| extension = RUBY_EXT |
| case GO: |
| extension = GO_EXT |
| } |
| |
| return extension |
| } |
| |
| func saveCode(action whisk.Action, filename string) (err error) { |
| var code string |
| var runtime string |
| var exec whisk.Exec |
| |
| exec = *action.Exec |
| runtime = strings.Split(exec.Kind, ":")[0] |
| |
| if strings.ToLower(runtime) == BLACKBOX && exec.Code == nil && *exec.Binary == false { |
| return cannotSaveImageError() |
| } else if strings.ToLower(runtime) == SEQUENCE { |
| return cannotSaveSequenceError() |
| } |
| |
| if exec.Code != nil { |
| code = *exec.Code |
| } |
| |
| if *exec.Binary { |
| decoded, _ := base64.StdEncoding.DecodeString(code) |
| code = string(decoded) |
| |
| if len(filename) == 0 { |
| filename = action.Name + getBinaryKindExtension(runtime) |
| } |
| } else { |
| if len(filename) == 0 { |
| filename = action.Name + getKindExtension(runtime) |
| } |
| } |
| |
| if exists, err := FileExists(filename); err != nil { |
| return err |
| } else if exists { |
| return fileExistsError(filename) |
| } |
| |
| if err := writeFile(filename, code); err != nil { |
| return err |
| } |
| |
| pwd, err := os.Getwd() |
| if err != nil { |
| whisk.Debug(whisk.DbgError, "os.Getwd() error: %s\n", err) |
| return err |
| } |
| |
| savedPath := fmt.Sprintf("%s%s%s", pwd, string(os.PathSeparator), filename) |
| |
| printSavedActionCodeSuccess(savedPath) |
| |
| return nil |
| } |
| |
| func webAction(webMode string, annotations whisk.KeyValueArr, entityName string, preserveAnnotations bool, existingAction *whisk.Action) (whisk.KeyValueArr, error) { |
| switch strings.ToLower(webMode) { |
| case "yes": |
| fallthrough |
| case "true": |
| return webActionAnnotations(preserveAnnotations, annotations, entityName, addWebAnnotations, existingAction) |
| case "no": |
| fallthrough |
| case "false": |
| return webActionAnnotations(preserveAnnotations, annotations, entityName, deleteWebAnnotations, existingAction) |
| case "raw": |
| return webActionAnnotations(preserveAnnotations, annotations, entityName, addRawAnnotations, existingAction) |
| default: |
| return nil, webInputError(webMode) |
| } |
| } |
| |
| type WebActionAnnotationMethod func(annotations whisk.KeyValueArr) whisk.KeyValueArr |
| |
| func webActionAnnotations( |
| preserveAnnotations bool, |
| annotations whisk.KeyValueArr, |
| entityName string, |
| webActionAnnotationMethod WebActionAnnotationMethod, |
| existingAction *whisk.Action) (whisk.KeyValueArr, error) { |
| |
| if preserveAnnotations && existingAction != nil { |
| annotations = whisk.KeyValueArr.AppendKeyValueArr(annotations, existingAction.Annotations) |
| } |
| |
| annotations = webActionAnnotationMethod(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 getWebActionAnnotations(action *whisk.Action) whisk.KeyValueArr { |
| var webKvArr = make(whisk.KeyValueArr, 3, 3) |
| var j = 0 |
| |
| var i = action.Annotations.FindKeyValue(WEB_EXPORT_ANNOT) |
| if i > -1 { |
| webKvArr[j] = action.Annotations[i] |
| j++ |
| } |
| i = action.Annotations.FindKeyValue(RAW_HTTP_ANNOT) |
| if i > -1 { |
| webKvArr[j] = action.Annotations[i] |
| j++ |
| } |
| i = action.Annotations.FindKeyValue(FINAL_ANNOT) |
| if i > -1 { |
| webKvArr[j] = action.Annotations[i] |
| j++ |
| } |
| return webKvArr[0:j] |
| } |
| |
| func getWebSecureAnnotations(action *whisk.Action) whisk.KeyValueArr { |
| var webKvArr = make(whisk.KeyValueArr, 1, 1) |
| var j = 0 |
| |
| var i = action.Annotations.FindKeyValue(WEB_SECURE_ANNOT) |
| if i > -1 { |
| webKvArr[j] = action.Annotations[i] |
| j++ |
| } |
| |
| return webKvArr[0:j] |
| } |
| |
| /* |
| * Update the existing annotations with the web security annotation |
| * If the current web security setting and existing setting are the "same", keep the existing value |
| * -> checking for the same "--web-secure true" setting means just checking if the two values are integers |
| * If the current web security setting is "false", remove any existing setting |
| */ |
| func updateWebSecureAnnotation(websecure string, annotations whisk.KeyValueArr) whisk.KeyValueArr { |
| secureSecret := webSecureSecret(websecure) // will be false when "--web-secure false" |
| existingSecret := annotations.GetValue(WEB_SECURE_ANNOT) |
| _, disableSecurity := secureSecret.(bool) |
| _, newSecretIsInt := secureSecret.(int64) |
| var existingSecretIsInt bool = false |
| if existingSecret != nil { |
| _, existingSecretIsInt = existingSecret.(json.Number) |
| } |
| |
| if existingSecretIsInt && newSecretIsInt { |
| whisk.Debug(whisk.DbgInfo, "Retaining existing secret number\n") |
| } else if existingSecret != nil && disableSecurity { |
| whisk.Debug(whisk.DbgInfo, "disabling web-secure; deleting annotation: %v\n", WEB_SECURE_ANNOT) |
| annotations = deleteKey(WEB_SECURE_ANNOT, annotations) |
| } else { |
| whisk.Debug(whisk.DbgInfo, "Setting %v annotation; prior secret %v new secret %v\n", |
| WEB_SECURE_ANNOT, reflect.TypeOf(existingSecret), reflect.TypeOf(secureSecret)) |
| annotations = annotations.AddOrReplace(&whisk.KeyValue{Key: WEB_SECURE_ANNOT, Value: secureSecret}) |
| } |
| |
| return annotations |
| } |
| |
| // |
| // Generate a secret according to the --web-secure setting |
| // true: return a random int64 |
| // false: return false, meaning no secret was returned |
| // string: return the same string |
| func webSecureSecret(webSecureMode string) interface{} { |
| switch strings.ToLower(webSecureMode) { |
| case "true": |
| return genWebActionSecureKey() |
| case "false": |
| return false |
| default: |
| return webSecureMode |
| } |
| } |
| |
| func getLimits(memorySet bool, logSizeSet bool, timeoutSet bool, concurrencySet bool, memory int, logSize int, timeout int, concurrency int) *whisk.Limits { |
| var limits *whisk.Limits |
| |
| if memorySet || logSizeSet || timeoutSet || concurrencySet { |
| limits = new(whisk.Limits) |
| |
| if memorySet { |
| limits.Memory = &memory |
| } |
| |
| if logSizeSet { |
| limits.Logsize = &logSize |
| } |
| |
| if timeoutSet { |
| limits.Timeout = &timeout |
| } |
| |
| if concurrencySet { |
| limits.Concurrency = &concurrency |
| } |
| } |
| |
| return limits |
| } |
| |
| func nestedError(errorMessage string, err error) error { |
| return whisk.MakeWskErrorFromWskError( |
| errors.New(errorMessage), |
| err, |
| whisk.EXIT_CODE_ERR_GENERAL, |
| whisk.DISPLAY_MSG, |
| whisk.DISPLAY_USAGE) |
| } |
| |
| func nonNestedError(errorMessage string) error { |
| return whisk.MakeWskError( |
| errors.New(errorMessage), |
| whisk.EXIT_CODE_ERR_USAGE, |
| 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.Name(), args, err) |
| |
| errMsg := wski18n.T( |
| "Invalid argument(s). {{.required}}", |
| map[string]interface{}{ |
| "required": 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 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, fetchCode bool, err error) error { |
| whisk.Debug(whisk.DbgError, "Client.Actions.Get(%s, %t) error: %s\n", entityName, fetchCode, 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) error { |
| whisk.Debug( |
| whisk.DbgError, |
| "Client.Actions.Invoke(%s) error: %s\n", |
| entityName, |
| 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 webSecureUsageError() error { |
| errMsg := wski18n.T("The --web-secure option is only valid when the --web option is enabled.") |
| 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 noArtifactError() error { |
| errMsg := wski18n.T("An action name and code artifact are required.") |
| |
| 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 cannotSaveImageError() error { |
| return nonNestedError(wski18n.T("Cannot save Docker images")) |
| } |
| |
| func cannotSaveSequenceError() error { |
| return nonNestedError(wski18n.T("Cannot save action sequences")) |
| } |
| |
| func fileExistsError(file string) error { |
| errMsg := wski18n.T("The file '{{.file}}' already exists", map[string]interface{}{ |
| "file": file, |
| }) |
| |
| 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( |
| qualifiedName QualifiedName, |
| blocking bool, |
| header bool, |
| response map[string]interface{}, |
| outputStream io.Writer) { |
| if header { |
| fmt.Fprintf( |
| outputStream, |
| wski18n.T( |
| "{{.ok}} invoked /{{.namespace}}/{{.name}} with id {{.id}}\n", |
| map[string]interface{}{ |
| "ok": color.GreenString("ok:"), |
| "namespace": boldString(qualifiedName.GetNamespace()), |
| "name": boldString(qualifiedName.GetEntityName()), |
| "id": boldString(getValueFromJSONResponse(ACTIVATION_ID, response)), |
| })) |
| } |
| |
| if 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 printActionGetWithURL(entityName string, actionURL string) { |
| fmt.Fprintf( |
| color.Output, |
| wski18n.T("{{.ok}} got action {{.name}}\n", |
| map[string]interface{}{ |
| "ok": color.GreenString("ok:"), |
| "name": boldString(entityName), |
| })) |
| fmt.Println(actionURL) |
| } |
| |
| 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), |
| })) |
| } |
| |
| func printSavedActionCodeSuccess(name string) { |
| fmt.Fprintf( |
| color.Output, |
| wski18n.T( |
| "{{.ok}} saved action code to {{.name}}\n", |
| map[string]interface{}{ |
| "ok": color.GreenString("ok:"), |
| "name": boldString(name), |
| })) |
| } |
| |
| // Generate a random number to be used as a web action's require-whisk-auth secret |
| func genWebActionSecureKey() int64 { |
| r := rand.New(rand.NewSource(time.Now().Unix())) |
| |
| // Truncate integer for API GW interoperability |
| return r.Int63() & MAX_JS_INT |
| } |
| |
| // Check if the specified action is a web-action |
| func isWebAction(client *whisk.Client, qname QualifiedName) (*whisk.Action, error) { |
| var err error = nil |
| |
| savedNs := client.Namespace |
| client.Namespace = qname.GetNamespace() |
| fullActionName := "/" + qname.GetNamespace() + "/" + qname.GetEntityName() |
| |
| action, _, err := client.Actions.Get(qname.GetEntityName(), DO_NOT_FETCH_CODE) |
| |
| if err != nil { |
| whisk.Debug(whisk.DbgError, "client.Actions.Get(%s, %t) error: %s\n", fullActionName, DO_NOT_FETCH_CODE, err) |
| whisk.Debug(whisk.DbgError, "Unable to obtain action '%s' for web action validation\n", fullActionName) |
| errMsg := wski18n.T("Unable to get action '{{.name}}': {{.err}}", |
| map[string]interface{}{"name": fullActionName, "err": err}) |
| err = whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_NETWORK, whisk.DISPLAY_MSG, |
| whisk.NO_DISPLAY_USAGE) |
| } else { |
| err = errors.New(wski18n.T("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})) |
| |
| if action.WebAction() { |
| err = nil |
| } |
| } |
| |
| client.Namespace = savedNs |
| |
| return action, err |
| } |
| |
| func init() { |
| actionCreateCmd.Flags().BoolVar(&Flags.action.native, "native", false, wski18n.T("treat ACTION as native action (zip file provides a compatible executable to run)")) |
| actionCreateCmd.Flags().StringVar(&Flags.action.docker, "docker", "", wski18n.T("use provided docker image (a path on DockerHub) to run the action")) |
| 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_FLAG, "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated")) |
| actionCreateCmd.Flags().IntVarP(&Flags.action.memory, MEMORY_FLAG, "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action")) |
| actionCreateCmd.Flags().IntVarP(&Flags.action.logsize, LOG_SIZE_FLAG, "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action")) |
| actionCreateCmd.Flags().IntVarP(&Flags.action.concurrency, CONCURRENCY_FLAG, "c", CONCURRENCY_LIMIT, wski18n.T("the maximum intra-container concurrent activation `LIMIT` 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_FLAG, "", 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")) |
| actionCreateCmd.Flags().StringVar(&Flags.action.websecure, WEB_SECURE_FLAG, "", wski18n.T("secure the web action. where `SECRET` is true, false, or any string. Only valid when the ACTION is a web action")) |
| |
| actionUpdateCmd.Flags().BoolVar(&Flags.action.native, "native", false, wski18n.T("treat ACTION as native action (zip file provides a compatible executable to run)")) |
| actionUpdateCmd.Flags().StringVar(&Flags.action.docker, "docker", "", wski18n.T("use provided docker image (a path on DockerHub) to run the action")) |
| 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_FLAG, "t", TIMEOUT_LIMIT, wski18n.T("the timeout `LIMIT` in milliseconds after which the action is terminated")) |
| actionUpdateCmd.Flags().IntVarP(&Flags.action.memory, MEMORY_FLAG, "m", MEMORY_LIMIT, wski18n.T("the maximum memory `LIMIT` in MB for the action")) |
| actionUpdateCmd.Flags().IntVarP(&Flags.action.logsize, LOG_SIZE_FLAG, "l", LOGSIZE_LIMIT, wski18n.T("the maximum log size `LIMIT` in MB for the action")) |
| actionUpdateCmd.Flags().IntVarP(&Flags.action.concurrency, CONCURRENCY_FLAG, "c", CONCURRENCY_LIMIT, wski18n.T("the maximum intra-container concurrent activation `LIMIT` 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_FLAG, "", 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().StringVar(&Flags.action.websecure, WEB_SECURE_FLAG, "", wski18n.T("secure the web action. where `SECRET` is true, false, or any string. Only valid when the ACTION is a web 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; parameters with prefix \"*\" are bound, \"**\" are bound and finalized")) |
| actionGetCmd.Flags().BoolVarP(&Flags.action.url, "url", "r", false, wski18n.T("get action url")) |
| actionGetCmd.Flags().StringVar(&Flags.action.saveAs, SAVE_AS_FLAG, "", wski18n.T("file to save action code to")) |
| actionGetCmd.Flags().BoolVarP(&Flags.action.save, SAVE_FLAG, "", false, wski18n.T("save action code to file corresponding with action name")) |
| |
| 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")) |
| actionListCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by entity name; only applicable within the limit/skip returned entity block")) |
| |
| actionCmd.AddCommand( |
| actionCreateCmd, |
| actionUpdateCmd, |
| actionInvokeCmd, |
| actionGetCmd, |
| actionDeleteCmd, |
| actionListCmd, |
| ) |
| } |