This is an automated email from the ASF dual-hosted git repository.

shwstppr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git


The following commit(s) were added to refs/heads/main by this push:
     new 79f09fb  Add support for excluding a list of parameters from an API 
response (#163)
79f09fb is described below

commit 79f09fbcdad6d9af6eac23976010c29f3efc4389
Author: Pearl Dsilva <pearl1...@gmail.com>
AuthorDate: Tue Aug 5 23:41:56 2025 -0400

    Add support for excluding a list of parameters from an API response (#163)
    
    This PR address 
#https://github.com/apache/cloudstack-cloudmonkey/issues/149, it adds an 
exclude parameter to remove fields from the response
    
    For example, without the exclude parameter, the listAccounts response looks 
like:
    
    ```
    $ ./cmk -p pd-env list accounts name=admin
    {
      "account": [
        {
          "accounttype": 1,
          "apikeyaccess": "INHERIT",
          "backupavailable": "Unlimited",
          "backuplimit": "Unlimited",
          "backupstorageavailable": "Unlimited",
          "backupstoragelimit": "Unlimited",
          "backupstoragetotal": 0,
          "backuptotal": 0,
          "bucketavailable": "Unlimited",
          "bucketlimit": "Unlimited",
          "buckettotal": 0,
          "cpuavailable": "Unlimited",
          "cpulimit": "Unlimited",
          "cputotal": 4,
          "domain": "ROOT",
          "domainid": "db0704ab-5827-11f0-804a-1e0052000469",
          "domainpath": "ROOT",
          "groups": [],
          "id": "24e85a59-5828-11f0-804a-1e0052000469",
          "ipavailable": "Unlimited",
          "iplimit": "Unlimited",
          "iptotal": 1,
          "isdefault": true,
          "memoryavailable": "Unlimited",
          "memorylimit": "Unlimited",
          "memorytotal": 4096,
          "name": "admin",
          "networkavailable": "Unlimited",
          "networklimit": "Unlimited",
          "networktotal": 1,
          "objectstorageavailable": "Unlimited",
          "objectstoragelimit": "Unlimited",
          "objectstoragetotal": 0,
          "primarystorageavailable": "Unlimited",
          "primarystoragelimit": "Unlimited",
          "primarystoragetotal": 16,
          "projectavailable": "Unlimited",
          "projectlimit": "Unlimited",
          "projecttotal": 0,
          "receivedbytes": 1346690740,
          "roleid": "fff4ee8f-5827-11f0-804a-1e0052000469",
          "rolename": "Root Admin",
          "roletype": "Admin",
          "secondarystorageavailable": "Unlimited",
          "secondarystoragelimit": "Unlimited",
          "secondarystoragetotal": 0,
          "sentbytes": 35867842,
          "snapshotavailable": "Unlimited",
          "snapshotlimit": "Unlimited",
          "snapshottotal": 0,
          "state": "enabled",
          "templateavailable": "Unlimited",
          "templatelimit": "Unlimited",
          "templatetotal": 0,
          "user": [
            {
              "account": "admin",
              "accountid": "24e85a59-5828-11f0-804a-1e0052000469",
              "accounttype": 1,
              "apikey": 
"LIN6rqXuaJwMPfGYFh13qDwYz5VNNz1J2J6qIOWcd3oLQOq0WtD4CwRundBL6rzXToa3lQOC_vKjI3nkHtiD8Q",
              "created": "2025-07-03T16:09:48+0000",
              "domain": "ROOT",
              "domainid": "db0704ab-5827-11f0-804a-1e0052000469",
              "firstname": "admin",
              "id": "24e902f0-5828-11f0-804a-1e0052000469",
              "is2faenabled": false,
              "is2famandated": false,
              "iscallerchilddomain": false,
              "isdefault": true,
              "lastname": "cloud",
              "roleid": "fff4ee8f-5827-11f0-804a-1e0052000469",
              "rolename": "Root Admin",
              "roletype": "Admin",
              "state": "enabled",
              "username": "admin",
              "usersource": "native"
            },
            {
              "account": "admin",
              "accountid": "24e85a59-5828-11f0-804a-1e0052000469",
              "accounttype": 1,
              "apikey": 
"U7qz8y6CjK1ECQsOuRLT7XaIfaWF3QuB4VJBHvDOBfQjyzsVvhWgDhMkHveJzu1Bb7oFnYKG4CZAfdHLpnim6w",
              "created": "2025-07-09T17:26:05+0000",
              "domain": "ROOT",
              "domainid": "db0704ab-5827-11f0-804a-1e0052000469",
              "email": "kubeadmin",
              "firstname": "admin",
              "id": "8ea65043-7df2-4906-8953-641a7e25a5cf",
              "is2faenabled": false,
              "is2famandated": false,
              "iscallerchilddomain": false,
              "isdefault": false,
              "lastname": "kubeadmin",
              "roleid": "fff4ee8f-5827-11f0-804a-1e0052000469",
              "rolename": "Root Admin",
              "roletype": "Admin",
              "state": "enabled",
              "username": "admin-kubeadmin",
              "usersource": "native"
            }
          ],
          "vmavailable": "Unlimited",
          "vmlimit": "Unlimited",
          "vmrunning": 2,
          "vmstopped": 0,
          "vmtotal": 2,
          "volumeavailable": "Unlimited",
          "volumelimit": "Unlimited",
          "volumetotal": 2,
          "vpcavailable": "Unlimited",
          "vpclimit": "Unlimited",
          "vpctotal": 0
        }
      ],
      "count": 1
    }
    
    ```
    
    With this patch, if user wants to remove `user` field from the response, 
they could do so using the exclude param:
    ```
    $ ./cmk -p pd-env list accounts name=admin exclude=user
    {
      "account": [
        {
          "accounttype": 1,
          "apikeyaccess": "INHERIT",
          "backupavailable": "Unlimited",
          "backuplimit": "Unlimited",
          "backupstorageavailable": "Unlimited",
          "backupstoragelimit": "Unlimited",
          "backupstoragetotal": 0,
          "backuptotal": 0,
          "bucketavailable": "Unlimited",
          "bucketlimit": "Unlimited",
          "buckettotal": 0,
          "cpuavailable": "Unlimited",
          "cpulimit": "Unlimited",
          "cputotal": 4,
          "domain": "ROOT",
          "domainid": "db0704ab-5827-11f0-804a-1e0052000469",
          "domainpath": "ROOT",
          "groups": [],
          "id": "24e85a59-5828-11f0-804a-1e0052000469",
          "ipavailable": "Unlimited",
          "iplimit": "Unlimited",
          "iptotal": 1,
          "isdefault": true,
          "memoryavailable": "Unlimited",
          "memorylimit": "Unlimited",
          "memorytotal": 4096,
          "name": "admin",
          "networkavailable": "Unlimited",
          "networklimit": "Unlimited",
          "networktotal": 1,
          "objectstorageavailable": "Unlimited",
          "objectstoragelimit": "Unlimited",
          "objectstoragetotal": 0,
          "primarystorageavailable": "Unlimited",
          "primarystoragelimit": "Unlimited",
          "primarystoragetotal": 16,
          "projectavailable": "Unlimited",
          "projectlimit": "Unlimited",
          "projecttotal": 0,
          "receivedbytes": 1346706148,
          "roleid": "fff4ee8f-5827-11f0-804a-1e0052000469",
          "rolename": "Root Admin",
          "roletype": "Admin",
          "secondarystorageavailable": "Unlimited",
          "secondarystoragelimit": "Unlimited",
          "secondarystoragetotal": 0,
          "sentbytes": 35872906,
          "snapshotavailable": "Unlimited",
          "snapshotlimit": "Unlimited",
          "snapshottotal": 0,
          "state": "enabled",
          "templateavailable": "Unlimited",
          "templatelimit": "Unlimited",
          "templatetotal": 0,
          "vmavailable": "Unlimited",
          "vmlimit": "Unlimited",
          "vmrunning": 2,
          "vmstopped": 0,
          "vmtotal": 2,
          "volumeavailable": "Unlimited",
          "volumelimit": "Unlimited",
          "volumetotal": 2,
          "vpcavailable": "Unlimited",
          "vpclimit": "Unlimited",
          "vpctotal": 0
        }
      ],
      "count": 1
    }
    
    ```
---
 cli/completer.go | 17 +++++++++++++++
 cmd/api.go       | 15 +++++++++++--
 cmd/output.go    | 64 +++++++++++++++++++++++++++++++++++---------------------
 config/cache.go  |  7 +++++++
 4 files changed, 77 insertions(+), 26 deletions(-)

diff --git a/cli/completer.go b/cli/completer.go
index be1ba21..53de5fb 100644
--- a/cli/completer.go
+++ b/cli/completer.go
@@ -357,6 +357,7 @@ func (t *autoCompleter) Do(line []rune, pos int) (options 
[][]rune, offset int)
                                }
                                return
                        }
+
                        if arg.Type == config.FAKE && arg.Name == "filter=" {
                                offset = 0
                                filterInputs := 
strings.Split(strings.Replace(argInput, ",", ",|", -1), "|")
@@ -373,6 +374,22 @@ func (t *autoCompleter) Do(line []rune, pos int) (options 
[][]rune, offset int)
                                return
                        }
 
+                       if arg.Type == config.FAKE && arg.Name == "exclude=" {
+                               offset = 0
+                               excludeFilterInputs := 
strings.Split(strings.Replace(argInput, ",", ",|", -1), "|")
+                               lastExcludeFilterInput := 
lastString(excludeFilterInputs)
+                               for _, key := range apiFound.ResponseKeys {
+                                       if inArray(key, excludeFilterInputs) {
+                                               continue
+                                       }
+                                       if strings.HasPrefix(key, 
lastExcludeFilterInput) {
+                                               options = append(options, 
[]rune(key[len(lastExcludeFilterInput):]))
+                                               offset = 
len(lastExcludeFilterInput)
+                                       }
+                               }
+                               return
+                       }
+
                        autocompleteAPI := findAutocompleteAPI(arg, apiFound, 
apiMap)
                        if autocompleteAPI == nil {
                                return nil, 0
diff --git a/cmd/api.go b/cmd/api.go
index 058c6fb..872a328 100644
--- a/cmd/api.go
+++ b/cmd/api.go
@@ -82,7 +82,7 @@ func init() {
                                if strings.HasSuffix(err.Error(), "context 
canceled") {
                                        return nil
                                } else if response != nil {
-                                       printResult(r.Config.Core.Output, 
response, nil)
+                                       printResult(r.Config.Core.Output, 
response, nil, nil)
                                }
                                return err
                        }
@@ -98,8 +98,19 @@ func init() {
                                }
                        }
 
+                       var excludeKeys []string
+                       for _, arg := range apiArgs {
+                               if strings.HasPrefix(arg, "exclude=") {
+                                       for _, excludeKey := range 
strings.Split(strings.Split(arg, "=")[1], ",") {
+                                               if 
len(strings.TrimSpace(excludeKey)) > 0 {
+                                                       excludeKeys = 
append(excludeKeys, strings.TrimSpace(excludeKey))
+                                               }
+                                       }
+                               }
+                       }
+
                        if len(response) > 0 {
-                               printResult(r.Config.Core.Output, response, 
filterKeys)
+                               printResult(r.Config.Core.Output, response, 
filterKeys, excludeKeys)
                        }
 
                        return nil
diff --git a/cmd/output.go b/cmd/output.go
index 08004cf..17606e6 100644
--- a/cmd/output.go
+++ b/cmd/output.go
@@ -206,51 +206,67 @@ func printCsv(response map[string]interface{}, filter 
[]string) {
        enc.Flush()
 }
 
-func filterResponse(response map[string]interface{}, filter []string, 
outputType string) map[string]interface{} {
-       if filter == nil || len(filter) == 0 {
+func filterResponse(response map[string]interface{}, filter []string, 
excludeFilter []string, outputType string) map[string]interface{} {
+       if (filter == nil || len(filter) == 0) && (excludeFilter == nil || 
len(excludeFilter) == 0) {
                return response
        }
+
+       excludeSet := make(map[string]struct{}, len(excludeFilter))
+       for _, key := range excludeFilter {
+               excludeSet[key] = struct{}{}
+       }
+
+       filterSet := make(map[string]struct{}, len(filter))
+       for _, key := range filter {
+               filterSet[key] = struct{}{}
+       }
+
        filteredResponse := make(map[string]interface{})
-       for k, v := range response {
-               valueType := reflect.TypeOf(v)
-               if valueType.Kind() == reflect.Slice || valueType.Kind() == 
reflect.Map {
-                       items, ok := v.([]interface{})
-                       if !ok {
-                               continue
-                       }
+
+       for key, value := range response {
+               switch items := value.(type) {
+               case []interface{}:
                        var filteredRows []interface{}
                        for _, item := range items {
                                row, ok := item.(map[string]interface{})
-                               if !ok || len(row) < 1 {
+                               if !ok || len(row) == 0 {
                                        continue
                                }
+
                                filteredRow := make(map[string]interface{})
-                               for _, filterKey := range filter {
-                                       for field := range row {
-                                               if filterKey == field {
-                                                       filteredRow[field] = 
row[field]
+
+                               if len(filter) > 0 {
+                                       // Include only keys that exist in 
filterSet
+                                       for filterKey := range filterSet {
+                                               if val, exists := 
row[filterKey]; exists {
+                                                       filteredRow[filterKey] 
= val
+                                               } else if outputType == 
config.COLUMN || outputType == config.CSV || outputType == config.TABLE {
+                                                       filteredRow[filterKey] 
= "" // Ensure all filter keys exist in row
                                                }
                                        }
-                                       if outputType == config.COLUMN || 
outputType == config.CSV || outputType == config.TABLE {
-                                               if _, ok := 
filteredRow[filterKey]; !ok {
-                                                       filteredRow[filterKey] 
= ""
+                               } else {
+                                       // Exclude keys from excludeFilter
+                                       for field, val := range row {
+                                               if _, excluded := 
excludeSet[field]; !excluded {
+                                                       filteredRow[field] = val
                                                }
                                        }
                                }
+
                                filteredRows = append(filteredRows, filteredRow)
                        }
-                       filteredResponse[k] = filteredRows
-               } else {
-                       filteredResponse[k] = v
-                       continue
-               }
+                       filteredResponse[key] = filteredRows
 
+               default:
+                       filteredResponse[key] = value
+               }
        }
+
        return filteredResponse
 }
 
-func printResult(outputType string, response map[string]interface{}, filter 
[]string) {
-       response = filterResponse(response, filter, outputType)
+func printResult(outputType string, response map[string]interface{}, filter 
[]string, excludeFilter []string) {
+       response = filterResponse(response, filter, excludeFilter, outputType)
        switch outputType {
        case config.JSON:
                printJSON(response)
diff --git a/config/cache.go b/config/cache.go
index 096582d..13596dd 100644
--- a/config/cache.go
+++ b/config/cache.go
@@ -151,6 +151,13 @@ func (c *Config) UpdateCache(response 
map[string]interface{}) interface{} {
                        Description: "cloudmonkey specific response key 
filtering",
                })
 
+               // Add exclude arg
+               apiArgs = append(apiArgs, &APIArg{
+                       Name:        "exclude=",
+                       Type:        FAKE,
+                       Description: "cloudmonkey specific response key to 
exlude when filtering",
+               })
+
                sort.Slice(apiArgs, func(i, j int) bool {
                        return apiArgs[i].Name < apiArgs[j].Name
                })

Reply via email to