Skip to content

Commit

Permalink
Allow CLI to sort entities by name (apache#2326)
Browse files Browse the repository at this point in the history
- Created interfaces `Printables` and `Sortables`
- Made Actions, Triggers, Packages, Rules, APIs into Printables and Sortables
- Made Activations into Printables and Sortables, Sort currently undefined
- Made alphabetic sorting default, sort by last update time with --time flag
- Changed sorting default back to last update time, --sort flag for alphabetical sorting
- Updated flag name to "--name-sort"/"-n"
- Updated Docs
- Fixed rule status printing for `wsk list` and `wsk namespace get`
  • Loading branch information
underwoodb-sd-ibm authored and csantanapr committed Aug 17, 2017
1 parent 9b26034 commit 6aae54c
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 141 deletions.
4 changes: 3 additions & 1 deletion commands/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ var actionListCmd = &cobra.Command{
return actionListError(qualifiedName.GetEntityName(), options, err)
}

printList(actions)
sortByName := flags.common.nameSort
printList(actions, sortByName)

return nil
},
Expand Down Expand Up @@ -942,6 +943,7 @@ func init() {

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,
Expand Down
2 changes: 1 addition & 1 deletion commands/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ var activationListCmd = &cobra.Command{
if options.Docs == true {
printFullActivationList(activations)
} else {
printList(activations)
printList(activations, false) // Default sorting for Activations are by creation time, hence sortByName is always false
}

return nil
Expand Down
120 changes: 76 additions & 44 deletions commands/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func isValidRelpath(relpath string) (error, bool) {
return nil, true
}


/*
* Pull the managedUrl (external API URL) from the API configuration
*/
Expand Down Expand Up @@ -445,6 +444,8 @@ var apiListCmd = &cobra.Command{
var retApiArray *whisk.RetApiArray
var apiPath string
var apiVerb string
var orderFilteredList []whisk.ApiFilteredList
var orderFilteredRow []whisk.ApiFilteredRow

if whiskErr := checkArgs(args, 0, 3, "Api list",
wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
Expand Down Expand Up @@ -518,18 +519,19 @@ var apiListCmd = &cobra.Command{
// Cast to a common type to allow for code to print out apilist response or apiget response
retApiArray = (*whisk.RetApiArray)(retApi)
}

//Checks for any order flags being passed
sortByName := flags.common.nameSort
// Display the APIs - applying any specified filtering
if (Flags.common.full) {
fmt.Fprintf(color.Output,
wski18n.T("{{.ok}} APIs\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
}))

for i:=0; i<len(retApiArray.Apis); i++ {
printFilteredListApi(retApiArray.Apis[i].ApiValue, apiPath, apiVerb)
for i := 0; i < len(retApiArray.Apis); i++ {
orderFilteredList = append(orderFilteredList, genFilteredList(retApiArray.Apis[i].ApiValue, apiPath, apiVerb)...)
}
printList(orderFilteredList, sortByName) // Sends an array of structs that contains specifed variables that are not truncated
} else {
if (len(retApiArray.Apis) > 0) {
// Dynamically create the output format string based on the maximum size of the
Expand All @@ -542,42 +544,43 @@ var apiListCmd = &cobra.Command{
map[string]interface{}{
"ok": color.GreenString("ok:"),
}))
fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
for i:=0; i<len(retApiArray.Apis); i++ {
printFilteredListRow(retApiArray.Apis[i].ApiValue, apiPath, apiVerb, maxActionNameSize, maxApiNameSize)
for i := 0; i < len(retApiArray.Apis); i++ {
orderFilteredRow = append(orderFilteredRow, genFilteredRow(retApiArray.Apis[i].ApiValue, apiPath, apiVerb, maxActionNameSize, maxApiNameSize)...)
}
printList(orderFilteredRow, sortByName) // Sends an array of structs that contains specifed variables that are truncated
} else {
fmt.Fprintf(color.Output,
wski18n.T("{{.ok}} APIs\n",
map[string]interface{}{
"ok": color.GreenString("ok:"),
}))
fmt.Printf(fmtString, "Action", "Verb", "API Name", "URL")
printList(orderFilteredRow, sortByName) // Sends empty orderFilteredRow so that defaultHeader can be printed
}
}

return nil
},
}

/*
* Takes an API object (containing one more more single basepath/relpath/operation triplets)
* and some filtering configuration. For each API endpoint matching the filtering criteria, display
* each endpoint's configuration - one line per configuration property (action name, verb, api name, api gw url)
*/
func printFilteredListApi(resultApi *whisk.RetApi, apiPath string, apiVerb string) {
// genFilteredList(resultApi, api) generates an array of
// ApiFilteredLists for the purpose of ordering and printing in a list form.
// NOTE: genFilteredRow() generates entries with one line per configuration
// property (action name, verb, api name, api gw url)
func genFilteredList(resultApi *whisk.RetApi, apiPath string, apiVerb string) []whisk.ApiFilteredList{
var orderInfo whisk.ApiFilteredList
var orderInfoArr []whisk.ApiFilteredList
baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
apiName := resultApi.Swagger.Info.Title
basePath := resultApi.Swagger.BasePath
if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
for path, _ := range resultApi.Swagger.Paths {
whisk.Debug(whisk.DbgInfo, "printFilteredListApi: comparing api relpath: %s\n", path)
whisk.Debug(whisk.DbgInfo, "genFilteredList: comparing api relpath: %s\n", path)
if ( len(apiPath) == 0 || path == apiPath) {
whisk.Debug(whisk.DbgInfo, "printFilteredListApi: relpath matches\n")
whisk.Debug(whisk.DbgInfo, "genFilteredList: relpath matches\n")
for op, opv := range resultApi.Swagger.Paths[path] {
whisk.Debug(whisk.DbgInfo, "printFilteredListApi: comparing operation: '%s'\n", op)
whisk.Debug(whisk.DbgInfo, "genFilteredList: comparing operation: '%s'\n", op)
if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
whisk.Debug(whisk.DbgInfo, "printFilteredListApi: operation matches: %#v\n", opv)
whisk.Debug(whisk.DbgInfo, "genFilteredList: operation matches: %#v\n", opv)
var actionName string
if (opv.XOpenWhisk == nil) {
actionName = ""
Expand All @@ -586,38 +589,36 @@ func printFilteredListApi(resultApi *whisk.RetApi, apiPath string, apiVerb strin
} else {
actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
}
fmt.Printf("%s: %s\n", wski18n.T("Action"), actionName)
fmt.Printf(" %s: %s\n", wski18n.T("API Name"), apiName)
fmt.Printf(" %s: %s\n", wski18n.T("Base path"), basePath)
fmt.Printf(" %s: %s\n", wski18n.T("Path"), path)
fmt.Printf(" %s: %s\n", wski18n.T("Verb"), op)
fmt.Printf(" %s: %s\n", wski18n.T("URL"), baseUrl+path)
orderInfo = AssignListInfo(actionName, op, apiName, basePath, path, baseUrl+path)
whisk.Debug(whisk.DbgInfo, "Appening to orderInfoArr: %s %s\n", orderInfo.RelPath)
orderInfoArr = append(orderInfoArr, orderInfo)
}
}
}
}
}
return orderInfoArr
}

/*
* Takes an API object (containing one more more single basepath/relpath/operation triplets)
* and some filtering configuration. For each API matching the filtering criteria, display the API
* on a single line (action name, verb, api name, api gw url).
*
* NOTE: Large action name and api name value will be truncated by their associated max size parameters.
*/
func printFilteredListRow(resultApi *whisk.RetApi, apiPath string, apiVerb string, maxActionNameSize int, maxApiNameSize int) {
// genFilteredRow(resultApi, api, maxApiNameSize, maxApiNameSize) generates an array of
// ApiFilteredRows for the purpose of ordering and printing in a list form by parsing and
// initializing vaules for each individual ApiFilteredRow struct.
// NOTE: Large action and api name values will be truncated by their associated max size parameters.
func genFilteredRow(resultApi *whisk.RetApi, apiPath string, apiVerb string, maxActionNameSize int, maxApiNameSize int) []whisk.ApiFilteredRow {
var orderInfo whisk.ApiFilteredRow
var orderInfoArr []whisk.ApiFilteredRow
baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
apiName := resultApi.Swagger.Info.Title
basePath := resultApi.Swagger.BasePath
if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
for path, _ := range resultApi.Swagger.Paths {
whisk.Debug(whisk.DbgInfo, "printFilteredListRow: comparing api relpath: %s\n", path)
whisk.Debug(whisk.DbgInfo, "genFilteredRow: comparing api relpath: %s\n", path)
if ( len(apiPath) == 0 || path == apiPath) {
whisk.Debug(whisk.DbgInfo, "printFilteredListRow: relpath matches\n")
whisk.Debug(whisk.DbgInfo, "genFilteredRow: relpath matches\n")
for op, opv := range resultApi.Swagger.Paths[path] {
whisk.Debug(whisk.DbgInfo, "printFilteredListRow: comparing operation: '%s'\n", op)
whisk.Debug(whisk.DbgInfo, "genFilteredRow: comparing operation: '%s'\n", op)
if ( len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb)) {
whisk.Debug(whisk.DbgInfo, "printFilteredListRow: operation matches: %#v\n", opv)
whisk.Debug(whisk.DbgInfo, "genFilteredRow: operation matches: %#v\n", opv)
var actionName string
if (opv.XOpenWhisk == nil) {
actionName = ""
Expand All @@ -626,21 +627,51 @@ func printFilteredListRow(resultApi *whisk.RetApi, apiPath string, apiVerb strin
} else {
actionName = "/"+opv.XOpenWhisk.Namespace+"/"+opv.XOpenWhisk.ActionName
}
fmt.Printf(fmtString,
actionName[0 : min(len(actionName), maxActionNameSize)],
op,
apiName[0 : min(len(apiName), maxApiNameSize)],
baseUrl+path)
orderInfo = AssignRowInfo(actionName[0 : min(len(actionName), maxActionNameSize)], op, apiName[0 : min(len(apiName), maxApiNameSize)], basePath, path, baseUrl+path)
orderInfo.FmtString = fmtString
whisk.Debug(whisk.DbgInfo, "Appening to orderInfoArr: %s %s\n", orderInfo.RelPath)
orderInfoArr = append(orderInfoArr, orderInfo)
}
}
}
}
}
return orderInfoArr
}

// AssignRowInfo(actionName, verb, apiName, basePath, relPath, url) assigns
// the given vaules to and initializes an ApiFilteredRow struct, then returns it.
func AssignRowInfo(actionName string, verb string, apiName string, basePath string, relPath string, url string) whisk.ApiFilteredRow {
var orderInfo whisk.ApiFilteredRow

orderInfo.ActionName = actionName
orderInfo.Verb = verb
orderInfo.ApiName = apiName
orderInfo.BasePath = basePath
orderInfo.RelPath = relPath
orderInfo.Url = url

return orderInfo
}

// AssignListInfo(actionName, verb, apiName, basePath, relPath, url) assigns
// the given vaules to and initializes an ApiFilteredList struct, then returns it.
func AssignListInfo(actionName string, verb string, apiName string, basePath string, relPath string, url string) whisk.ApiFilteredList {
var orderInfo whisk.ApiFilteredList

orderInfo.ActionName = actionName
orderInfo.Verb = verb
orderInfo.ApiName = apiName
orderInfo.BasePath = basePath
orderInfo.RelPath = relPath
orderInfo.Url = url

return orderInfo
}

func getLargestActionNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVerb string) int {
var maxNameSize = 0
for i:=0; i<len(retApiArray.Apis); i++ {
for i := 0; i < len(retApiArray.Apis); i++ {
var resultApi = retApiArray.Apis[i].ApiValue
if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
for path, _ := range resultApi.Swagger.Paths {
Expand Down Expand Up @@ -673,7 +704,7 @@ func getLargestActionNameSize(retApiArray *whisk.RetApiArray, apiPath string, ap

func getLargestApiNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVerb string) int {
var maxNameSize = 0
for i:=0; i<len(retApiArray.Apis); i++ {
for i := 0; i < len(retApiArray.Apis); i++ {
var resultApi = retApiArray.Apis[i].ApiValue
apiName := resultApi.Swagger.Info.Title
if (resultApi.Swagger != nil && resultApi.Swagger.Paths != nil) {
Expand Down Expand Up @@ -920,6 +951,7 @@ func init() {
apiGetCmd.Flags().StringVarP(&Flags.common.format, "format", "", formatOptionJson, wski18n.T("Specify the API output `TYPE`, either json or yaml"))
apiListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
apiListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
apiListCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("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"))
apiListCmd.Flags().BoolVarP(&Flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
apiCmd.AddCommand(
apiCreateCmd,
Expand Down
1 change: 1 addition & 0 deletions commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type FlagsStruct struct {
feed string // name of feed
detail bool
format string
nameSort bool // sorts list alphabetically by entity name
}

property struct {
Expand Down
25 changes: 20 additions & 5 deletions commands/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var namespaceListCmd = &cobra.Command{
werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_NETWORK, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
printList(namespaces)
printList(namespaces, false) // `-n` flag applies to `namespace get`, not list, so must pass value false for printList here
return nil
},
}
Expand Down Expand Up @@ -99,16 +99,31 @@ var namespaceGetCmd = &cobra.Command{

fmt.Fprintf(color.Output, wski18n.T("Entities in namespace: {{.namespace}}\n",
map[string]interface{}{"namespace": boldString(getClientNamespace())}))
printList(namespace.Contents.Packages)
printList(namespace.Contents.Actions)
printList(namespace.Contents.Triggers)
printList(namespace.Contents.Rules)
sortByName := flags.common.nameSort
printList(namespace.Contents.Packages, sortByName)
printList(namespace.Contents.Actions, sortByName)
printList(namespace.Contents.Triggers, sortByName)
//No errors, lets attempt to retrieve the status of each rule #312
for index, rule := range namespace.Contents.Rules {
ruleStatus, _, err := client.Rules.Get(rule.Name)
if err != nil {
errStr := wski18n.T("Unable to get status of rule '{{.name}}': {{.err}}",
map[string]interface{}{"name": rule.Name, "err": err})
fmt.Println(errStr)
werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
namespace.Contents.Rules[index].Status = ruleStatus.Status
}
printList(namespace.Contents.Rules, sortByName)

return nil
},
}

func init() {
namespaceGetCmd.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"))

namespaceCmd.AddCommand(
namespaceListCmd,
namespaceGetCmd,
Expand Down
5 changes: 4 additions & 1 deletion commands/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,8 @@ var packageListCmd = &cobra.Command{
return werr
}

printList(packages)
sortByName := flags.common.nameSort
printList(packages, sortByName)

return nil
},
Expand Down Expand Up @@ -503,6 +504,7 @@ var packageRefreshCmd = &cobra.Command{
}

func init() {
<<<<<<< HEAD
packageCreateCmd.Flags().StringSliceVarP(&Flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format"))
packageCreateCmd.Flags().StringVarP(&Flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format"))
packageCreateCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
Expand All @@ -524,6 +526,7 @@ func init() {

packageListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of packages from the result"))
packageListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of packages from the collection"))
packageListCmd.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"))

packageCmd.AddCommand(
packageBindCmd,
Expand Down
5 changes: 4 additions & 1 deletion commands/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,9 @@ var ruleListCmd = &cobra.Command{
rules[index].Status = ruleStatus.Status
}
}
printList(rules)

sortByName := flags.common.nameSort
printList(rules, sortByName)
return nil
},
}
Expand All @@ -414,6 +416,7 @@ func init() {

ruleListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of rules from the result"))
ruleListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of rules from the collection"))
ruleListCmd.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"))

ruleCmd.AddCommand(
ruleCreateCmd,
Expand Down
4 changes: 3 additions & 1 deletion commands/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ var triggerListCmd = &cobra.Command{
werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
return werr
}
printList(triggers)
sortByName := flags.common.nameSort
printList(triggers, sortByName)
return nil
},
}
Expand Down Expand Up @@ -510,6 +511,7 @@ func init() {

triggerListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of triggers from the result"))
triggerListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of triggers from the collection"))
triggerListCmd.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"))

triggerCmd.AddCommand(
triggerFireCmd,
Expand Down
Loading

0 comments on commit 6aae54c

Please sign in to comment.