blob: 775989b81f8be897bde42b58fcc3df9afa383090 [file] [log] [blame]
/*
* 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 (
"bufio"
"errors"
"fmt"
"strings"
"github.com/apache/incubator-openwhisk-cli/wski18n"
"github.com/apache/incubator-openwhisk-client-go/whisk"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
//prettyjson "github.com/hokaccha/go-prettyjson" // See prettyjson comment below
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"encoding/json"
"io"
"io/ioutil"
"os"
"reflect"
"regexp"
"sort"
)
func csvToQualifiedActions(artifacts string) []string {
var res []string
actions := strings.Split(artifacts, ",")
for i := 0; i < len(actions); i++ {
res = append(res, getQualifiedName(actions[i], Properties.Namespace))
}
return res
}
/**
* Processes command line to retrieve pairs of key-value pairs, where the value must be valid JSON.
*
* Parameters and annotations are handled the same way. The flag here is only for generating an error messages
* specific to one or the other.
*
* NOTE: this function will exit in case of a processing error since it indicates a problem parsing parameters.
*
* @return either an array or a JSON object (map) formatted representation of the key-value pairs.
*/
func getParameters(params []string, keyValueFormat bool, annotation bool) interface{} {
var parameters interface{}
var err error
if !annotation {
whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", params)
} else {
whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", params)
}
parameters, err = getJSONFromStrings(params, keyValueFormat)
if err != nil {
whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, %s) failed: %s\n", params, keyValueFormat, err)
var errStr string
if !annotation {
errStr = wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}",
map[string]interface{}{"param": fmt.Sprintf("%#v", params), "err": err})
} else {
errStr = wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}",
map[string]interface{}{"annotation": fmt.Sprintf("%#v", params), "err": err})
}
werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
ExitOnError(werr)
}
return parameters
}
func getJSONFromStrings(content []string, keyValueFormat bool) (interface{}, error) {
var data map[string]interface{}
var res interface{}
whisk.Debug(whisk.DbgInfo, "Convert content to JSON: %#v\n", content)
for i := 0; i < len(content); i++ {
dc := json.NewDecoder(strings.NewReader(content[i]))
dc.UseNumber()
if err := dc.Decode(&data); err != nil {
whisk.Debug(whisk.DbgError, "Invalid JSON detected for '%s' \n", content[i])
return whisk.KeyValueArr{}, err
}
whisk.Debug(whisk.DbgInfo, "Created map '%v' from '%v'\n", data, content[i])
}
if data == nil {
data = make(map[string]interface{})
}
if keyValueFormat {
res = getKeyValueFormattedJSON(data)
} else {
res = data
}
return res, nil
}
func getKeyValueFormattedJSON(data map[string]interface{}) whisk.KeyValueArr {
var keyValueArr whisk.KeyValueArr
for key, value := range data {
keyValue := whisk.KeyValue{
Key: key,
Value: value,
}
keyValueArr = append(keyValueArr, keyValue)
}
whisk.Debug(whisk.DbgInfo, "Created key/value format '%v' from '%v'\n", keyValueArr, data)
return keyValueArr
}
func getFormattedJSON(key string, value string) string {
var res string
key = getEscapedJSON(key)
if isValidJSON(value) {
whisk.Debug(whisk.DbgInfo, "Value '%s' is valid JSON.\n", value)
res = fmt.Sprintf("{\"%s\": %s}", key, value)
} else {
whisk.Debug(whisk.DbgInfo, "Converting value '%s' to a string as it is not valid JSON.\n", value)
res = fmt.Sprintf("{\"%s\": \"%s\"}", key, value)
}
whisk.Debug(whisk.DbgInfo, "Formatted JSON '%s'\n", res)
return res
}
func getEscapedJSON(value string) string {
value = strings.Replace(value, "\\", "\\\\", -1)
value = strings.Replace(value, "\"", "\\\"", -1)
return value
}
func isValidJSON(value string) bool {
var jsonInterface interface{}
err := json.Unmarshal([]byte(value), &jsonInterface)
return err == nil
}
var boldString = color.New(color.Bold).SprintFunc()
type Sortables []whisk.Sortable
// Uses quickSort to sort commands based on their compare methods
// Param: Takes in a array of Sortable interfaces which contains a specific command
func Swap(sortables Sortables, i, j int) { sortables[i], sortables[j] = sortables[j], sortables[i] }
func toPrintable(sortable []whisk.Sortable) []whisk.Printable {
sortedPrintable := make([]whisk.Printable, len(sortable), len(sortable))
for i := range sortable {
sortedPrintable[i] = sortable[i].(whisk.Printable)
}
return sortedPrintable
}
// Prints the parameters/list for wsk xxxx list
// Identifies type and then copies array into an array of interfaces(Sortable) to be sorted and printed
// Param: Takes in an interace which contains an array of a command(Ex: []Action)
func printList(collection interface{}, sortByName bool) {
var commandToSort []whisk.Sortable
switch collection := collection.(type) {
case []whisk.Action:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.Trigger:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.Package:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.Rule:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.Namespace:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.Activation:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.ApiFilteredList:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
case []whisk.ApiFilteredRow:
for i := range collection {
commandToSort = append(commandToSort, collection[i])
}
}
if sortByName && len(commandToSort) > 0 {
quickSort(commandToSort, 0, len(commandToSort)-1)
}
printCommandsList(toPrintable(commandToSort), makeDefaultHeader(collection))
}
func quickSort(toSort Sortables, left int, right int) {
low := left
high := right
pivot := toSort[(left+right)/2]
for low <= high {
for toSort[low].Compare(pivot) {
low++
}
for pivot.Compare(toSort[high]) {
high--
}
if low <= high {
Swap(toSort, low, high)
low++
high--
}
}
if left < high {
quickSort(toSort, left, high)
}
if low < right {
quickSort(toSort, low, right)
}
}
// makeDefaultHeader(collection) returns the default header to be used in case
// the list to be printed is empty.
func makeDefaultHeader(collection interface{}) string {
defaultHeader := reflect.TypeOf(collection).String()
defaultHeader = strings.ToLower(defaultHeader[8:] + "s") // Removes '[]whisk.' from `[]whisk.ENTITY_TYPE`
if defaultHeader == "apifilteredrows" {
defaultHeader = fmt.Sprintf("%-30s %7s %20s %s", "Action", "Verb", "API Name", "URL")
} else if defaultHeader == "apifilteredlists" {
defaultHeader = ""
}
return defaultHeader
}
func stripTimestamp(log string) (strippedLog string) {
regex := regexp.MustCompile("[a-zA-Z0-9\\s]*(stdout|stderr):\\s(.*)")
match := regex.FindStringSubmatch(log)
if len(match) > 2 && len(match[2]) > 0 {
strippedLog = match[2]
} else {
strippedLog = log
}
return strippedLog
}
func printFullList(collection interface{}) {
switch collection := collection.(type) {
case []whisk.Action:
case []whisk.Trigger:
case []whisk.Package:
case []whisk.Rule:
case []whisk.Namespace:
case []whisk.Activation:
printFullActivationList(collection)
}
}
func printSummary(collection interface{}) {
switch collection := collection.(type) {
case *whisk.Action:
printActionSummary(collection)
case *whisk.Trigger:
printTriggerSummary(collection)
case *whisk.Package:
printPackageSummary(collection)
case *whisk.Rule:
case *whisk.Namespace:
case *whisk.Activation:
}
}
// Used to print Action, Tigger, Package, and Rule lists
// Param: Takes in a array of Printable interface, and the name of the command
// being sent to it
// **Note**: The name sould be an empty string for APIs.
func printCommandsList(commands []whisk.Printable, defaultHeader string) {
if len(commands) != 0 {
fmt.Fprint(color.Output, boldString(commands[0].ToHeaderString()))
for i := range commands {
fmt.Print(commands[i].ToSummaryRowString())
}
} else {
fmt.Fprintf(color.Output, "%s\n", boldString(defaultHeader))
}
}
func printFullActivationList(activations []whisk.Activation) {
fmt.Fprintf(color.Output, "%s\n", boldString("activations"))
for _, activation := range activations {
printJSON(activation)
}
}
func printStrippedActivationLogs(logs []string) {
for _, log := range logs {
fmt.Printf("%s\n", stripTimestamp(log))
}
}
func printActivationLogs(logs []string) {
for _, log := range logs {
fmt.Printf("%s\n", log)
}
}
func printArrayContents(arrStr []string) {
for _, str := range arrStr {
fmt.Printf("%s\n", str)
}
}
func printPackageSummary(pkg *whisk.Package) {
printEntitySummary(fmt.Sprintf("%7s", "package"), getFullName(pkg.Namespace, pkg.Name, ""),
getValueString(pkg.Annotations, "description"),
strings.Join(getParamUnion(pkg.Annotations, pkg.Parameters, "name"), ", "))
if pkg.Actions != nil {
for _, action := range pkg.Actions {
paramUnion := getParamUnion(action.Annotations, action.Parameters, "name")
printEntitySummary(fmt.Sprintf("%7s", "action"), getFullName(pkg.Namespace, pkg.Name, action.Name),
getValueString(action.Annotations, "description"),
strings.Join(paramUnion, ", "))
}
}
if pkg.Feeds != nil {
for _, feed := range pkg.Feeds {
printEntitySummary(fmt.Sprintf("%7s", "feed "), getFullName(pkg.Namespace, pkg.Name, feed.Name),
getValueString(feed.Annotations, "description"),
strings.Join(getParamUnion(feed.Annotations, feed.Parameters, "name"), ", "))
}
}
}
func printActionSummary(action *whisk.Action) {
paramUnion := getParamUnion(action.Annotations, action.Parameters, "name")
printEntitySummary(fmt.Sprintf("%6s", "action"),
getFullName(action.Namespace, "", action.Name),
getValueString(action.Annotations, "description"),
strings.Join(paramUnion, ", "))
}
func printTriggerSummary(trigger *whisk.Trigger) {
printEntitySummary(fmt.Sprintf("%7s", "trigger"),
getFullName(trigger.Namespace, "", trigger.Name),
getValueString(trigger.Annotations, "description"),
strings.Join(getParamUnion(trigger.Annotations, trigger.Parameters, "name"), ", "))
}
func printRuleSummary(rule *whisk.Rule) {
fmt.Fprintf(color.Output, "%s %s\n", boldString(fmt.Sprintf("%4s", "rule")),
getFullName(rule.Namespace, "", rule.Name))
fmt.Fprintf(color.Output, " (%s: %s)\n", boldString(wski18n.T("status")), rule.Status)
}
func printEntitySummary(entityType string, fullName string, description string, params string) {
emptyParams := "none defined"
if len(params) <= 0 {
params = emptyParams
}
if len(description) > 0 {
fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, description)
} else if params != emptyParams {
descriptionFromParams := buildParamDescription(params)
fmt.Fprintf(color.Output, "%s %s: %s\n", boldString(entityType), fullName, descriptionFromParams)
} else {
fmt.Fprintf(color.Output, "%s %s\n", boldString(entityType), fullName)
}
fmt.Fprintf(color.Output, " (%s: %s)\n", boldString(wski18n.T("parameters")), params)
}
// getParamUnion(keyValArrAnnots, keyValArrParams, key) returns the union
// of parameters listed under annotations (keyValArrAnnots, using key) and
// bound parameters (keyValArrParams). Bound parameters will be denoted with
// a prefixed "*", and finalized bound parameters (can't be changed by
// user) will be denoted by a prefixed "**".
func getParamUnion(keyValArrAnnots whisk.KeyValueArr, keyValArrParams whisk.KeyValueArr, key string) []string {
var res []string
tag := "*"
if getValueBool(keyValArrAnnots, "final") {
tag = "**"
}
boundParams := getKeys(keyValArrParams)
annotatedParams := getChildValueStrings(keyValArrAnnots, "parameters", key)
res = append(boundParams, annotatedParams...) // Create union of boundParams and annotatedParams with duplication
for i := 0; i < len(res); i++ {
for j := i + 1; j < len(res); j++ {
if res[i] == res[j] {
res = append(res[:j], res[j+1:]...) // Remove duplicate entry
}
}
}
sort.Strings(res)
res = tagBoundParams(boundParams, res, tag)
return res
}
// tagBoundParams(boundParams, paramUnion, tag) returns the list paramUnion with
// all strings listed under boundParams set with a prefix tag.
func tagBoundParams(boundParams []string, paramUnion []string, tag string) []string {
res := paramUnion
for i := 0; i < len(boundParams); i++ {
for j := 0; j < len(res); j++ {
if boundParams[i] == res[j] {
res[j] = fmt.Sprintf("%s%s", tag, res[j])
}
}
}
return res
}
// buildParamDescription(params) returns a default entity description for
// `$ wsk [ENTITY] get [ENTITY_NAME] --summary` when parameters are defined,
// but the entity description under annotations is not.
func buildParamDescription(params string) string {
preamble := "Returns a result based on parameter"
params = strings.Replace(params, "*", "", -1)
temp := strings.Split(params, ",")
if len(temp) > 1 {
lastParam := temp[len(temp)-1]
newParams := strings.Replace(params, fmt.Sprintf(",%s", lastParam), fmt.Sprintf(" and%s", lastParam), 1)
return fmt.Sprintf("%ss %s", preamble, newParams)
}
return fmt.Sprintf("%s %s", preamble, params)
}
func getFullName(namespace string, packageName string, entityName string) string {
var fullName string
if len(namespace) > 0 && len(packageName) > 0 && len(entityName) > 0 {
fullName = fmt.Sprintf("/%s/%s/%s", namespace, packageName, entityName)
} else if len(namespace) > 0 && len(packageName) > 0 {
fullName = fmt.Sprintf("/%s/%s", namespace, packageName)
} else if len(namespace) > 0 && len(entityName) > 0 {
fullName = fmt.Sprintf("/%s/%s", namespace, entityName)
} else if len(namespace) > 0 {
fullName = fmt.Sprintf("/%s", namespace)
}
return fullName
}
func deleteKey(key string, keyValueArr whisk.KeyValueArr) whisk.KeyValueArr {
for i := 0; i < len(keyValueArr); i++ {
if keyValueArr[i].Key == key {
keyValueArr = append(keyValueArr[:i], keyValueArr[i+1:]...)
break
}
}
return keyValueArr
}
func addKeyValue(key string, value interface{}, keyValueArr whisk.KeyValueArr) whisk.KeyValueArr {
keyValue := whisk.KeyValue{
Key: key,
Value: value,
}
return append(keyValueArr, keyValue)
}
func getKeys(keyValueArr whisk.KeyValueArr) []string {
var res []string
for i := 0; i < len(keyValueArr); i++ {
res = append(res, keyValueArr[i].Key)
}
sort.Strings(res)
whisk.Debug(whisk.DbgInfo, "Got keys '%v' from '%v'\n", res, keyValueArr)
return res
}
func getValueString(keyValueArr whisk.KeyValueArr, key string) string {
var value interface{}
var res string
value = keyValueArr.GetValue(key)
castedValue, canCast := value.(string)
if canCast {
res = castedValue
}
whisk.Debug(whisk.DbgInfo, "Got string value '%v' for key '%s'\n", res, key)
return res
}
func getValueBool(keyValueArr whisk.KeyValueArr, key string) bool {
var value interface{}
var res bool
value = keyValueArr.GetValue(key)
castedValue, canCast := value.(bool)
if canCast {
res = castedValue
}
whisk.Debug(whisk.DbgInfo, "Got bool value '%v' for key '%s'\n", res, key)
return res
}
func getChildValues(keyValueArr whisk.KeyValueArr, key string, childKey string) []interface{} {
var value interface{}
var res []interface{}
value = keyValueArr.GetValue(key)
castedValue, canCast := value.([]interface{})
if canCast {
for i := 0; i < len(castedValue); i++ {
castedValue, canCast := castedValue[i].(map[string]interface{})
if canCast {
for subKey, subValue := range castedValue {
if subKey == childKey {
res = append(res, subValue)
}
}
}
}
}
whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
childKey)
return res
}
func getChildValueStrings(keyValueArr whisk.KeyValueArr, key string, childKey string) []string {
var keys []interface{}
var res []string
keys = getChildValues(keyValueArr, key, childKey)
for i := 0; i < len(keys); i++ {
castedValue, canCast := keys[i].(string)
if canCast {
res = append(res, castedValue)
}
}
sort.Strings(res)
whisk.Debug(whisk.DbgInfo, "Got values '%s' from '%v' for key '%s' and child key '%s'\n", res, keyValueArr, key,
childKey)
return res
}
func getValueFromJSONResponse(field string, response map[string]interface{}) interface{} {
var res interface{}
for key, value := range response {
if key == field {
res = value
break
}
}
return res
}
func logoText() string {
logo := `
____ ___ _ _ _ _ _
/\ \ / _ \ _ __ ___ _ __ | | | | |__ (_)___| | __
/\ /__\ \ | | | | '_ \ / _ \ '_ \| | | | '_ \| / __| |/ /
/ \____ \ / | |_| | |_) | __/ | | | |/\| | | | | \__ \ <
\ \ / \/ \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
\___\/ tm |_|
`
return logo
}
func printJSON(v interface{}, stream ...io.Writer) {
// Can't use prettyjson util issue https://github.com/hokaccha/go-prettyjson/issues/1 is fixed
//output, _ := prettyjson.Marshal(v)
//
//if len(stream) > 0 {
// fmt.Fprintf(stream[0], string(output))
//} else {
// fmt.Fprintf(color.Output, string(output))
//}
printJsonNoColor(v, stream...)
}
func printJsonNoColor(decoded interface{}, stream ...io.Writer) {
var output bytes.Buffer
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
encoder.Encode(&decoded)
json.Indent(&output, buffer.Bytes(), "", " ")
if len(stream) > 0 {
fmt.Fprintf(stream[0], "%s", string(output.Bytes()))
} else {
fmt.Fprintf(os.Stdout, "%s", string(output.Bytes()))
}
}
func unpackGzip(inpath string, outpath string) error {
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
}
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)
if err != nil {
whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", outpath, err)
errStr := wski18n.T("Error creating unGzip file '{{.name}}': {{.err}}",
map[string]interface{}{"name": outpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
defer unGzFile.Close()
gzFile, err := os.Open(inpath)
if err != nil {
whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
errStr := wski18n.T("Error opening Gzip file '{{.name}}': {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
defer gzFile.Close()
gzReader, err := gzip.NewReader(gzFile)
if err != nil {
whisk.Debug(whisk.DbgError, "gzip.NewReader() failed: %s\n", err)
errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
_, err = io.Copy(unGzFile, gzReader)
if err != nil {
whisk.Debug(whisk.DbgError, "io.Copy() failed: %s\n", err)
errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
return nil
}
func unpackZip(inpath string) error {
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)
errStr := wski18n.T("Unable to opens '{{.name}}' for unzipping: {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
defer zipFileReader.Close()
// Loop through the files in the zipfile
for _, item := range zipFileReader.File {
itemName := item.Name
itemType := item.Mode()
whisk.Debug(whisk.DbgInfo, "file item - %#v\n", item)
if itemType.IsDir() {
if err := os.MkdirAll(item.Name, item.Mode()); err != nil {
whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode(), err)
errStr := wski18n.T("Unable to create directory '{{.dir}}' while unzipping '{{.name}}': {{.err}}",
map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
}
if itemType.IsRegular() {
unzipFile, err := item.Open()
defer unzipFile.Close()
if err != nil {
whisk.Debug(whisk.DbgError, "'%s' Open() failed: %s\n", item.Name, err)
errStr := wski18n.T("Unable to open zipped file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
targetFile, err := os.Create(itemName)
if err != nil {
whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", itemName, err)
errStr := wski18n.T("Unable to create file '{{.file}}' while unzipping '{{.name}}': {{.err}}",
map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
if _, err := io.Copy(targetFile, unzipFile); err != nil {
whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", itemName, err)
errStr := wski18n.T("Unable to unzip file '{{.name}}': {{.err}}",
map[string]interface{}{"name": itemName, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
}
}
return nil
}
func unpackTar(inpath string) error {
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
}
tarFileReader, err := os.Open(inpath)
if err != nil {
whisk.Debug(whisk.DbgError, "os.Open(%s) failed: %s\n", inpath, err)
errStr := wski18n.T("Error opening tar file '{{.name}}': {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
defer tarFileReader.Close()
// Loop through the files in the tarfile
tReader := tar.NewReader(tarFileReader)
for {
item, err := tReader.Next()
if err == io.EOF {
whisk.Debug(whisk.DbgError, "EOF reach during untar\n")
break // end of tar
}
if err != nil {
whisk.Debug(whisk.DbgError, "tReader.Next() failed: %s\n", err)
errStr := wski18n.T("Error reading tar file '{{.name}}': {{.err}}",
map[string]interface{}{"name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
whisk.Debug(whisk.DbgInfo, "tar file item - %#v\n", item)
switch item.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(item.Name, os.FileMode(item.Mode)); err != nil {
whisk.Debug(whisk.DbgError, "os.MkdirAll(%s, %d) failed: %s\n", item.Name, item.Mode, err)
errStr := wski18n.T("Unable to create directory '{{.dir}}' while untarring '{{.name}}': {{.err}}",
map[string]interface{}{"dir": item.Name, "name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
case tar.TypeReg:
untarFile, err := os.OpenFile(item.Name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(item.Mode))
defer untarFile.Close()
if err != nil {
whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", item.Name, err)
errStr := wski18n.T("Unable to create file '{{.file}}' while untarring '{{.name}}': {{.err}}",
map[string]interface{}{"file": item.Name, "name": inpath, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
if _, err := io.Copy(untarFile, tReader); err != nil {
whisk.Debug(whisk.DbgError, "io.Copy() of '%s' failed: %s\n", item.Name, err)
errStr := wski18n.T("Unable to untar file '{{.name}}': {{.err}}",
map[string]interface{}{"name": item.Name, "err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
default:
whisk.Debug(whisk.DbgError, "Unexpected tar file type of %q\n", item.Typeflag)
errStr := wski18n.T("Unable to untar '{{.name}}' due to unexpected tar file type\n",
map[string]interface{}{"name": item.Name})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
}
return nil
}
func CheckArgs(args []string, minimumArgNumber int, maximumArgNumber int, commandName string,
requiredArgMsg string) *whisk.WskError {
exactlyOrAtLeast := wski18n.T("exactly")
exactlyOrNoMoreThan := wski18n.T("exactly")
if minimumArgNumber != maximumArgNumber {
exactlyOrAtLeast = wski18n.T("at least")
exactlyOrNoMoreThan = wski18n.T("no more than")
}
if len(args) < minimumArgNumber {
whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
exactlyOrAtLeast, minimumArgNumber))
errMsg := wski18n.T("Invalid argument(s). {{.required}}", map[string]interface{}{"required": requiredArgMsg})
whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
return whiskErr
} else if len(args) > maximumArgNumber {
whisk.Debug(whisk.DbgError, fmt.Sprintf("%s command must have %s %d argument(s)\n", commandName,
exactlyOrNoMoreThan, maximumArgNumber))
errMsg := wski18n.T("Invalid argument(s): {{.args}}. {{.required}}",
map[string]interface{}{"args": strings.Join(args[maximumArgNumber:], ", "), "required": requiredArgMsg})
whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
return whiskErr
} else {
return nil
}
}
func normalizeNamespace(namespace string) string {
if namespace == "_" {
namespace = wski18n.T("default")
}
return namespace
}
func getClientNamespace() string {
return normalizeNamespace(Client.Config.Namespace)
}
func ReadFile(filename string) (string, error) {
exists, err := FileExists(filename)
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": filename,
})
whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_USAGE,
whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
return "", whiskErr
}
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 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)
return "", whiskErr
}
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()
for i := 0; i < element.NumField(); i++ {
if strings.ToLower(element.Type().Field(i).Name) == strings.ToLower(field) {
return true
}
}
return false
}
func printField(value interface{}, field string) {
var matchFunc = func(structField string) bool {
return strings.ToLower(structField) == strings.ToLower(field)
}
structValue := reflect.ValueOf(value)
fieldValue := reflect.Indirect(structValue).FieldByNameFunc(matchFunc)
printJSON(fieldValue.Interface())
}
func parseShared(shared string) (bool, bool, error) {
var isShared, isSet bool
if strings.ToLower(shared) == "yes" {
isShared = true
isSet = true
} else if strings.ToLower(shared) == "no" {
isShared = false
isSet = true
} else if len(shared) == 0 {
isSet = false
} else {
whisk.Debug(whisk.DbgError, "Cannot use value '%s' for shared.\n", shared)
errMsg := wski18n.T("Cannot use value '{{.arg}}' for shared.", map[string]interface{}{"arg": shared})
whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG,
whisk.DISPLAY_USAGE)
return false, false, whiskErr
}
whisk.Debug(whisk.DbgError, "Sharing is '%t'\n", isShared)
return isShared, isSet, nil
}
func max(a int, b int) int {
if a > b {
return a
}
return b
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
func ReadProps(path string) (map[string]string, error) {
props := map[string]string{}
file, err := os.Open(path)
if err != nil {
// If file does not exist, just return props
whisk.Debug(whisk.DbgWarn, "Unable to read whisk properties file '%s' (file open error: %s); falling back to default properties\n", path, err)
return props, nil
}
defer file.Close()
lines := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
props = map[string]string{}
for _, line := range lines {
re := regexp.MustCompile("#.*")
line = re.ReplaceAllString(line, "")
line = strings.TrimSpace(line)
kv := strings.Split(line, "=")
if len(kv) != 2 {
// Invalid format; skip
continue
}
props[kv[0]] = kv[1]
}
return props, nil
}
func WriteProps(path string, props map[string]string) error {
file, err := os.Create(path)
if err != nil {
whisk.Debug(whisk.DbgError, "os.Create(%s) failed: %s\n", path, err)
errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
for key, value := range props {
line := fmt.Sprintf("%s=%s", strings.ToUpper(key), value)
_, err = fmt.Fprintln(writer, line)
if err != nil {
whisk.Debug(whisk.DbgError, "fmt.Fprintln() write to '%s' failed: %s\n", path, err)
errStr := wski18n.T("Whisk properties file write failure: {{.err}}", map[string]interface{}{"err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
}
return nil
}
func getSpaceGuid() (string, error) {
// get current props
props, err := ReadProps(Properties.PropsFile)
if err != nil {
whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
errStr := wski18n.T("Unable to obtain the `auth` property value: {{.err}}", map[string]interface{}{"err": err})
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return "", werr
}
// get the auth key and parse out the space guid
if authToken, hasProp := props["AUTH"]; hasProp {
spaceGuid := strings.Split(authToken, ":")[0]
return spaceGuid, nil
}
whisk.Debug(whisk.DbgError, "auth not found in properties: %#q\n", props)
errStr := wski18n.T("Auth key property value is not set")
werr := whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return "", werr
}
func isBlockingTimeout(err error) bool {
var blockingTimeout bool
whiskErr, isWhiskErr := err.(*whisk.WskError)
if isWhiskErr && whiskErr.TimedOut {
blockingTimeout = true
}
return blockingTimeout
}
func isApplicationError(err error) bool {
var applicationError bool
whiskErr, isWhiskErr := err.(*whisk.WskError)
if isWhiskErr && whiskErr.ApplicationError {
applicationError = true
}
return applicationError
}
func contains(arr []string, element string) bool {
for _, e := range arr {
if e == element {
return true
}
}
return false
}
func ExitOnError(err error) {
if err == nil {
return
}
whisk.Debug(whisk.DbgInfo, "err object type: %s\n", reflect.TypeOf(err).String())
T := wski18n.T
var exitCode int = 0
var displayUsage bool = false
var displayMsg bool = false
var msgDisplayed bool = true
var displayPrefix bool = true
werr, isWskError := err.(*whisk.WskError) // Is the err a WskError?
if isWskError {
whisk.Debug(whisk.DbgError, "Got a *whisk.WskError error: %#v\n", werr)
displayUsage = werr.DisplayUsage
displayMsg = werr.DisplayMsg
msgDisplayed = werr.MsgDisplayed
displayPrefix = werr.DisplayPrefix
exitCode = werr.ExitCode
} else {
whisk.Debug(whisk.DbgError, "Got some other error: %s\n", err)
fmt.Fprintf(os.Stderr, "%s\n", err)
displayUsage = false // Cobra already displayed the usage message
exitCode = 1
}
outputStream := colorable.NewColorableStderr()
// If the err msg should be displayed to the console and it has not already been
// displayed, display it now.
if displayMsg && !msgDisplayed && displayPrefix && exitCode != 0 {
fmt.Fprintf(outputStream, "%s%s\n", color.RedString(T("error: ")), err)
} else if displayMsg && !msgDisplayed && !displayPrefix && exitCode != 0 {
fmt.Fprintf(outputStream, "%s\n", err)
} else if displayMsg && !msgDisplayed && exitCode == 0 {
fmt.Fprintf(outputStream, "%s\n", err)
}
// Displays usage
if displayUsage {
fmt.Fprintf(outputStream, T("Run '{{.Name}} --help' for usage.\n",
map[string]interface{}{"Name": WskCmd.CommandPath()}))
}
os.Exit(exitCode)
}