openwhisk-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From csantan...@apache.org
Subject [incubator-openwhisk-cli] 02/07: Allow CLI to Save Code from Action (#2544)
Date Thu, 19 Oct 2017 14:01:58 GMT
This is an automated email from the ASF dual-hosted git repository.

csantanapr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-cli.git

commit c692c980d2ef4efa4b730383c2d478031b4700aa
Author: James Dubee <jwdubee@us.ibm.com>
AuthorDate: Wed Oct 18 13:54:01 2017 -0400

    Allow CLI to Save Code from Action (#2544)
    
    * Allow CLI to Save Code from Action
    
    * Formatting changes
    
    * Test refactor
    
    * Update test
    
    * Refactor
    
    * Review updates
    
    * Review updates
---
 commands/action.go               | 187 ++++++++++++++++++++++++++++++++-------
 commands/flags.go                |  15 +++-
 commands/util.go                 | 135 ++++++++++++++++++++++------
 wski18n/resources/en_US.all.json |  47 ++++++++--
 4 files changed, 315 insertions(+), 69 deletions(-)

diff --git a/commands/action.go b/commands/action.go
index b80bfb0..efbf842 100644
--- a/commands/action.go
+++ b/commands/action.go
@@ -24,6 +24,7 @@ import (
     "path/filepath"
     "io"
     "strings"
+    "os"
 
     "github.com/apache/incubator-openwhisk-client-go/whisk"
     "github.com/apache/incubator-openwhisk-cli/wski18n"
@@ -33,13 +34,29 @@ import (
     "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"
+const (
+    MEMORY_LIMIT      = 256
+    TIMEOUT_LIMIT     = 60000
+    LOGSIZE_LIMIT     = 10
+    ACTIVATION_ID     = "activationId"
+    WEB_EXPORT_ANNOT  = "web-export"
+    RAW_HTTP_ANNOT    = "raw-http"
+    FINAL_ANNOT       = "final"
+    NODE_JS_EXT       = ".js"
+    PYTHON_EXT        = ".py"
+    JAVA_EXT          = ".jar"
+    SWIFT_EXT         = ".swift"
+    ZIP_EXT           = ".zip"
+    PHP_EXT           = ".php"
+    NODE_JS           = "nodejs"
+    PYTHON            = "python"
+    JAVA              = "java"
+    SWIFT             = "swift"
+    PHP               = "php"
+    DEFAULT           = "default"
+    BLACKBOX          = "blackbox"
+    SEQUENCE          = "sequence"
+)
 
 var actionCmd = &cobra.Command{
     Use:   "action",
@@ -243,6 +260,8 @@ var actionGetCmd = &cobra.Command{
             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)
@@ -399,7 +418,7 @@ func parseAction(cmd *cobra.Command, args []string, update bool) (*whisk.Action,
     } else if Flags.action.sequence {
         if len(args) == 2 {
             action.Exec = new(whisk.Exec)
-            action.Exec.Kind = "sequence"
+            action.Exec.Kind = SEQUENCE
             action.Exec.Components = csvToQualifiedActions(args[1])
         } else {
             return nil, noArtifactError()
@@ -445,8 +464,7 @@ func getExec(args []string, params ActionFlags) (*whisk.Exec, error) {
             return nil, err
         }
 
-        if ext == ".zip" || ext == ".jar" {
-            // Base64 encode the file
+        if ext == ZIP_EXT || ext == JAVA_EXT {
             code = base64.StdEncoding.EncodeToString([]byte(code))
         }
 
@@ -460,24 +478,24 @@ func getExec(args []string, params ActionFlags) (*whisk.Exec, error)
{
     if len(kind) > 0 {
         exec.Kind = kind
     } else if len(docker) > 0 || isNative {
-        exec.Kind = "blackbox"
+        exec.Kind = BLACKBOX
         if isNative {
             exec.Image = "openwhisk/dockerskeleton"
         } else {
             exec.Image = docker
         }
-    } 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 == ".php" {
-        exec.Kind = "php:default"
+    } 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 == ".zip" {
+        if ext == ZIP_EXT {
             return nil, zipKindError()
         } else {
             return nil, extensionError(ext)
@@ -496,6 +514,86 @@ func getExec(args []string, params ActionFlags) (*whisk.Exec, error)
{
     return exec, nil
 }
 
+func getBinaryKindExtension(runtime string) (extension string) {
+    switch strings.ToLower(runtime) {
+    case JAVA:
+        extension = JAVA_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)
+    }
+
+    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 {
+        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) (whisk.KeyValueArr, error){
     switch strings.ToLower(webMode) {
     case "yes":
@@ -770,6 +868,22 @@ func javaEntryError() (error) {
     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,
@@ -877,6 +991,17 @@ func printActionDeleted(entityName string) {
             }))
 }
 
+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),
+            }))
+}
+
 // Check if the specified action is a web-action
 func isWebAction(client *whisk.Client, qname QualifiedName) (error) {
     var err error = nil
@@ -915,14 +1040,14 @@ func init() {
     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().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().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"))
+    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"))
 
     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"))
@@ -930,14 +1055,14 @@ func init() {
     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().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().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"))
+    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"))
 
     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"))
@@ -946,6 +1071,8 @@ func init() {
 
     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"))
diff --git a/commands/flags.go b/commands/flags.go
index b30d04f..8a06d05 100644
--- a/commands/flags.go
+++ b/commands/flags.go
@@ -25,10 +25,15 @@ import (
 // Flags //
 ///////////
 
-const MEMORY_FLAG   = "memory"
-const LOG_SIZE_FLAG = "logsize"
-const TIMEOUT_FLAG  = "timeout"
-const WEB_FLAG      = "web"
+const (
+    MEMORY_FLAG     = "memory"
+    LOG_SIZE_FLAG   = "logsize"
+    TIMEOUT_FLAG    = "timeout"
+    WEB_FLAG        = "web"
+    SAVE_FLAG       = "save"
+    SAVE_AS_FLAG    = "save-as"
+)
+
 
 var cliDebug = os.Getenv("WSK_CLI_DEBUG")  // Useful for tracing init() code
 
@@ -139,6 +144,8 @@ type ActionFlags struct {
     kind        string
     main        string
     url         bool
+    save        bool
+    saveAs     string
 }
 
 func IsVerbose() bool {
diff --git a/commands/util.go b/commands/util.go
index a4056ad..e5ec6ee 100644
--- a/commands/util.go
+++ b/commands/util.go
@@ -586,21 +586,37 @@ func printJsonNoColor(decoded interface{}, stream ...io.Writer) {
 }
 
 func unpackGzip(inpath string, outpath string) error {
-    // Make sure the target file does not exist
-    if _, err := os.Stat(outpath); err == nil {
-        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' exists\n", outpath)
+    var exists bool
+    var err error
+
+    exists, err = FileExists(outpath)
+
+    if err != nil {
+        return err
+    }
+
+    if exists {
         errStr := wski18n.T("The file '{{.name}}' already exists.  Delete it and retry.",
             map[string]interface{}{"name": outpath})
         werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
whisk.NO_DISPLAY_USAGE)
         return werr
     }
 
-    // Make sure the input file exists
-    if _, err := os.Stat(inpath); err != nil {
-        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
-        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name":
inpath})
-        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
whisk.NO_DISPLAY_USAGE)
-        return werr
+    exists, err = FileExists(inpath)
+
+    if err != nil {
+        return err
+    }
+
+    if !exists {
+        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
+            map[string]interface{}{
+                "name": inpath,
+            })
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+
+        return whiskErr
     }
 
     unGzFile, err := os.Create(outpath)
@@ -645,14 +661,22 @@ func unpackGzip(inpath string, outpath string) error {
 }
 
 func unpackZip(inpath string) error {
-    // Make sure the input file exists
-    if _, err := os.Stat(inpath); err != nil {
-        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
-        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name":
inpath})
-        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
whisk.NO_DISPLAY_USAGE)
-        return werr
+    exists, err := FileExists(inpath)
+
+    if err != nil {
+        return err
     }
 
+    if !exists {
+        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
+            map[string]interface{}{
+                "name": inpath,
+            })
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+
+        return whiskErr
+    }
     zipFileReader, err := zip.OpenReader(inpath)
     if err != nil {
         whisk.Debug(whisk.DbgError, "zip.OpenReader(%s) failed: %s\n", inpath, err)
@@ -712,13 +736,21 @@ func unpackZip(inpath string) error {
 }
 
 func unpackTar(inpath string) error {
+    exists, err := FileExists(inpath)
 
-    // Make sure the input file exists
-    if _, err := os.Stat(inpath); err != nil {
-        whisk.Debug(whisk.DbgError, "os.Stat reports file '%s' does not exist\n", inpath)
-        errStr := wski18n.T("The file '{{.name}}' does not exist.", map[string]interface{}{"name":
inpath})
-        werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
whisk.NO_DISPLAY_USAGE)
-        return werr
+    if err != nil {
+        return err
+    }
+
+    if !exists {
+        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
+            map[string]interface{}{
+                "name": inpath,
+            })
+        whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
+            whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+
+        return whiskErr
     }
 
     tarFileReader, err := os.Open(inpath)
@@ -828,11 +860,17 @@ func getClientNamespace() (string) {
 }
 
 func ReadFile(filename string) (string, error) {
-    _, err := os.Stat(filename)
+    exists, err := FileExists(filename)
+
     if err != nil {
-        whisk.Debug(whisk.DbgError, "os.Stat(%s) error: %s\n", filename, err)
-        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
-                map[string]interface{}{"name": filename, "err": err})
+        return "", err
+    }
+
+    if !exists {
+        errMsg := wski18n.T("File '{{.name}}' is not a valid file or it does not exist",
+            map[string]interface{}{
+                "name": filename,
+            })
         whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
             whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
 
@@ -842,7 +880,7 @@ func ReadFile(filename string) (string, error) {
     file, err := ioutil.ReadFile(filename)
     if err != nil {
         whisk.Debug(whisk.DbgError, "os.ioutil.ReadFile(%s) error: %s\n", filename, err)
-        errMsg := wski18n.T("Unable to read '{{.name}}': {{.err}}",
+        errMsg := wski18n.T("Unable to read the file '{{.name}}': {{.err}}",
                 map[string]interface{}{"name": filename, "err": err})
         whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
             whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
@@ -852,6 +890,51 @@ func ReadFile(filename string) (string, error) {
     return string(file), nil
 }
 
+func writeFile(filename string, content string) (error) {
+    file, err := os.Create(filename)
+
+    if err != nil {
+        whisk.Debug(whisk.DbgError, "os.Create(%s) error: %#v\n", filename, err)
+        errMsg := wski18n.T("Cannot create file '{{.name}}': {{.err}}",
+            map[string]interface{}{"name": filename, "err": err})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE, whisk.DISPLAY_MSG,
+            whisk.DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    defer file.Close()
+
+    if _, err = file.WriteString(content); err != nil {
+        whisk.Debug(whisk.DbgError, "File.WriteString(%s) error: %#v\n", content, err)
+        errMsg := wski18n.T("Cannot create file '{{.name}}': {{.err}}",
+            map[string]interface{}{"name": filename, "err": err})
+        whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE, whisk.DISPLAY_MSG,
+            whisk.DISPLAY_USAGE)
+        return whiskErr
+    }
+
+    return nil
+}
+
+func FileExists(file string) (bool, error) {
+    _, err := os.Stat(file)
+
+    if err != nil {
+        if os.IsNotExist(err) == true {
+            return false, nil
+        } else {
+            whisk.Debug(whisk.DbgError, "os.Stat(%s) error: %#v\n", file, err)
+            errMsg := wski18n.T("Cannot access file '{{.name}}': {{.err}}",
+                map[string]interface{}{"name": file, "err": err})
+            whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_USAGE,
+                whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+            return true, whiskErr
+        }
+    }
+
+    return true, nil
+}
+
 func fieldExists(value interface{}, field string) (bool) {
     element := reflect.ValueOf(value).Elem()
 
diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json
index 3236830..acc6e2c 100644
--- a/wski18n/resources/en_US.all.json
+++ b/wski18n/resources/en_US.all.json
@@ -732,8 +732,8 @@
     "translation": "shared"
   },
   {
-    "id": "The file '{{.name}}' does not exist.",
-    "translation": "The file '{{.name}}' does not exist."
+    "id": "File '{{.name}}' is not a valid file or it does not exist",
+    "translation": "File '{{.name}}' is not a valid file or it does not exist"
   },
   {
     "id": "Error creating unGzip file '{{.name}}': {{.err}}",
@@ -876,12 +876,8 @@
     "translation": "Unable to get action '{{.name}}': {{.err}}"
   },
   {
-    "id": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}",
-    "translation": "File '{{.name}}' is not a valid file or it does not exist: {{.err}}"
-  },
-  {
-    "id": "Unable to read '{{.name}}': {{.err}}",
-    "translation": "Unable to read '{{.name}}': {{.err}}"
+    "id": "Unable to read the file '{{.name}}': {{.err}}",
+    "translation": "Unable to read the file '{{.name}}': {{.err}}"
   },
   {
     "id": "'{{.name}}' is not a supported action runtime",
@@ -1526,4 +1522,37 @@
   {
     "id": "sorts a list alphabetically by order of [BASE_PATH | API_NAME], API_PATH, then
API_VERB; only applicable within the limit/skip returned entity block",
     "translation": "sorts a list alphabetically by order of [BASE_PATH | API_NAME], API_PATH,
then API_VERB; only applicable within the limit/skip returned entity block"
-  }]
+  },
+  {
+    "id": "prints bash command completion script to stdout",
+    "translation": "prints bash command completion script to stdout"
+  },
+  {
+    "id": "save action code to file corresponding with action name",
+    "translation": "save action code to file corresponding with action name"
+  },
+  {
+    "id": "file to save action code to",
+    "translation": "file to save action code to"
+  },
+  {
+    "id": "The file '{{.file}}' already exists",
+    "translation": "The file '{{.file}}' already exists"
+  },
+  {
+    "id": "{{.ok}} saved action code to {{.name}}\n",
+    "translation": "{{.ok}} saved action code to {{.name}}\n"
+  },
+  {
+    "id": "Cannot save Docker images",
+    "translation": "Cannot save Docker images"
+  },
+  {
+    "id": "Cannot save action sequences",
+    "translation": "Cannot save action sequences"
+  },
+  {
+    "id": "Cannot create file '{{.name}}': {{.err}}",
+    "translation": "Cannot create file '{{.name}}': {{.err}}"
+  }
+]

-- 
To stop receiving notification emails like this one, please contact
"commits@openwhisk.apache.org" <commits@openwhisk.apache.org>.

Mime
View raw message