Repository: digitalocean/doctl
Pull request #1277 (2022-10-11 18:35:46)

Re-writes activations list command subtree in Go
This PR re-write the `doctl sls activations` subtree in native go-lang. All existing behavior is maintained. 

joshuaauerbachwatson: @ddebarros I am seeing commits in this PR that were already merged in the earlier PR.   Did this get properly rebased?


Patch:
diff --git a/commands/activations.go b/commands/activations.go
index d621fd87..042eec2b 100644
--- a/commands/activations.go
+++ b/commands/activations.go
@@ -23,6 +23,7 @@ import (
 	"github.com/apache/openwhisk-client-go/whisk"
 	"github.com/digitalocean/doctl"
 	"github.com/digitalocean/doctl/commands/charm/text"
+	"github.com/digitalocean/doctl/commands/displayers"
 	"github.com/spf13/cobra"
 )
 
@@ -58,14 +59,16 @@ logs.`,
 	AddStringFlag(get, "function", "f", "", "Fetch activations for a specific function")
 	AddBoolFlag(get, "quiet", "q", false, "Suppress last activation information header")
 
-	list := CmdBuilder(cmd, RunActivationsList, "list [<activation_name>]", "Lists Activations for which records exist",
+	list := CmdBuilder(cmd, RunActivationsList, "list [<function_name>]", "Lists Activations for which records exist",
 		`Use `+"`"+`doctl serverless activations list`+"`"+` to list the activation records that are present in the cloud for previously
 invoked functions.`,
-		Writer)
-	AddStringFlag(list, "limit", "l", "", "only return LIMIT number of activations (default 30, max 200)")
-	AddStringFlag(list, "skip", "s", "", "exclude the first SKIP number of activations from the result")
-	AddStringFlag(list, "since", "", "", "return activations with timestamps later than SINCE; measured in milliseconds since Th, 01, Jan 1970")
-	AddStringFlag(list, "upto", "", "", "return activations with timestamps earlier than UPTO; measured in milliseconds since Th, 01, Jan 1970")
+		Writer,
+		displayerType(&displayers.Activation{}),
+	)
+	AddIntFlag(list, "limit", "l", 30, "only return LIMIT number of activations (default 30, max 200)")
+	AddIntFlag(list, "skip", "s", 0, "exclude the first SKIP number of activations from the result")
+	AddIntFlag(list, "since", "", 0, "return activations with timestamps later than SINCE; measured in milliseconds since Th, 01, Jan 1970")
+	AddIntFlag(list, "upto", "", 0, "return activations with timestamps earlier than UPTO; measured in milliseconds since Th, 01, Jan 1970")
 	AddBoolFlag(list, "count", "", false, "show only the total number of activations")
 	AddBoolFlag(list, "full", "f", false, "include full activation description")
 
@@ -172,28 +175,12 @@ func RunActivationsGet(c *CmdConfig) error {
 func makeBanner(writer io.Writer, activation whisk.Activation) {
 	end := time.UnixMilli(activation.End).Format("01/02 03:04:05")
 	init := text.NewStyled("=== ").Muted()
-	body := fmt.Sprintf("%s %s %s %s:%s", activation.ActivationID, statusToString(activation.StatusCode),
+	body := fmt.Sprintf("%s %s %s %s:%s", activation.ActivationID, displayers.GetActivationStatus(activation.StatusCode),
 		end, activation.Name, activation.Version)
 	msg := text.NewStyled(body).Highlight()
 	fmt.Fprintln(writer, init.String()+msg.String())
 }
 
-// statusToString converts numeric status codes to typical string
-func statusToString(statusCode int) string {
-	switch statusCode {
-	case 0:
-		return "success"
-	case 1:
-		return "application error"
-	case 2:
-		return "developer error"
-	case 3:
-		return "system error"
-	default:
-		return "??"
-	}
-}
-
 // printLog is a subroutine for printing just the logs of an activation
 func printLogs(writer io.Writer, strip bool, activation whisk.Activation) {
 	for _, log := range activation.Logs {
@@ -245,14 +232,57 @@ func printActivationRecord(writer io.Writer, activation whisk.Activation) {
 // RunActivationsList supports the 'activations list' command
 func RunActivationsList(c *CmdConfig) error {
 	argCount := len(c.Args)
+
 	if argCount > 1 {
 		return doctl.NewTooManyArgsErr(c.NS)
 	}
-	output, err := RunServerlessExec(activationList, c, []string{flagCount, flagFull}, []string{flagLimit, flagSkip, flagSince, flagUpto})
+	sls := c.Serverless()
+
+	var name string
+	if argCount > 0 {
+		name = c.Args[0]
+	}
+
+	countFlags, _ := c.Doit.GetBool(c.NS, flagCount)
+	fullFlag, _ := c.Doit.GetBool(c.NS, flagFull)
+	skipFlag, _ := c.Doit.GetInt(c.NS, flagSkip)
+	sinceFlag, _ := c.Doit.GetInt(c.NS, flagSince)
+	upToFlag, _ := c.Doit.GetInt(c.NS, flagUpto)
+	limitFlag, _ := c.Doit.GetInt(c.NS, flagLimit)
+
+	limit := limitFlag
+	if limitFlag > 200 {
+		limit = 200
+	}
+
+	if countFlags {
+		options := whisk.ActivationCountOptions{Since: int64(sinceFlag), Upto: int64(upToFlag), Name: name}
+		count, err := sls.GetActivationCount(options)
+		if err != nil {
+			return err
+		}
+
+		if name != "" {
+			fmt.Fprintf(c.Out, "You have %d activations in this namespace for function %s \n", count.Activations, name)
+		} else {
+			fmt.Fprintf(c.Out, "You have %d activations in this namespace \n", count.Activations)
+		}
+		return nil
+	}
+
+	options := whisk.ActivationListOptions{Limit: limit, Skip: skipFlag, Since: int64(sinceFlag), Upto: int64(upToFlag), Docs: fullFlag, Name: name}
+
+	actv, err := sls.ListActivations(options)
 	if err != nil {
 		return err
 	}
-	return c.PrintServerlessTextOutput(output)
+
+	items := &displayers.Activation{Activations: actv}
+	if fullFlag {
+		return items.JSON(c.Out)
+	}
+
+	return c.Display(items)
 }
 
 // RunActivationsLogs supports the 'activations logs' command
diff --git a/commands/displayers/activations.go b/commands/displayers/activations.go
new file mode 100644
index 00000000..f7d30fbf
--- /dev/null
+++ b/commands/displayers/activations.go
@@ -0,0 +1,120 @@
+package displayers
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
+	"github.com/apache/openwhisk-client-go/whisk"
+)
+
+type Activation struct {
+	Activations []whisk.Activation
+}
+
+var _ Displayable = &Activation{}
+
+// ColMap implements Displayable
+func (a *Activation) ColMap() map[string]string {
+	return map[string]string{
+		"Datetime":     "Datetime",
+		"Status":       "Status",
+		"Kind":         "Kind",
+		"Version":      "Version",
+		"ActivationID": "Activation ID",
+		"Start":        "Start",
+		"Wait":         "Wait",
+		"Duration":     "Duration",
+		"Function":     "Function",
+	}
+}
+
+// Cols implements Displayable
+func (a *Activation) Cols() []string {
+	return []string{
+		"Datetime",
+		"Status",
+		"Kind",
+		"Version",
+		"ActivationID",
+		"Start",
+		"Wait",
+		"Duration",
+		"Function",
+	}
+}
+
+// JSON implements Displayable
+func (a *Activation) JSON(out io.Writer) error {
+	return writeJSON(a.Activations, out)
+}
+
+// KV implements Displayable
+func (a *Activation) KV() []map[string]interface{} {
+	out := make([]map[string]interface{}, 0, len(a.Activations))
+
+	for _, actv := range a.Activations {
+		o := map[string]interface{}{
+			"Datetime":     time.UnixMilli(actv.Start).Format("01/02 03:04:05"),
+			"Status":       GetActivationStatus(actv.StatusCode),
+			"Kind":         getActivationAnnotationValue(actv, "kind"),
+			"Version":      actv.Version,
+			"ActivationID": actv.ActivationID,
+			"Start":        getActivationStartType(actv),
+			"Wait":         getActivationAnnotationValue(actv, "waitTime"),
+			"Duration":     fmt.Sprintf("%dms", actv.Duration),
+			"Function":     getActivationFunctionName(actv),
+		}
+		out = append(out, o)
+	}
+	return out
+}
+
+func getActivationStartType(a whisk.Activation) string {
+	if getActivationAnnotationValue(a, "init") == "" {
+		return "cold"
+	}
+	return "warm"
+}
+
+// Gets the full function name for the activation.
+func getActivationFunctionName(a whisk.Activation) string {
+	name := a.Name
+	path := getActivationAnnotationValue(a, "path")
+
+	if path == nil {
+		return name
+	}
+
+	parts := strings.Split(path.(string), "/")
+
+	if len(parts) == 3 {
+		return parts[1] + "/" + name
+	}
+
+	return name
+}
+
+func getActivationAnnotationValue(a whisk.Activation, key string) interface{} {
+	if a.Annotations == nil {
+		return nil
+	}
+	return a.Annotations.GetValue(key)
+}
+
+// converts numeric status codes to typical string
+func GetActivationStatus(statusCode int) string {
+	switch statusCode {
+	case 0:
+		return "success"
+	case 1:
+		return "application error"
+	case 2:
+		return "developer error"
+	case 3:
+		return "system error"
+	default:
+		return "unknown"
+	}
+}
diff --git a/do/mocks/ServerlessService.go b/do/mocks/ServerlessService.go
index 68a24429..ca8c9014 100644
--- a/do/mocks/ServerlessService.go
+++ b/do/mocks/ServerlessService.go
@@ -139,6 +139,21 @@ func (mr *MockServerlessServiceMockRecorder) GetActivation(arg0 interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivation", reflect.TypeOf((*MockServerlessService)(nil).GetActivation), arg0)
 }
 
+// GetActivationCount mocks base method.
+func (m *MockServerlessService) GetActivationCount(arg0 whisk.ActivationCountOptions) (whisk.ActivationCount, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetActivationCount", arg0)
+	ret0, _ := ret[0].(whisk.ActivationCount)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetActivationCount indicates an expected call of GetActivationCount.
+func (mr *MockServerlessServiceMockRecorder) GetActivationCount(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivationCount", reflect.TypeOf((*MockServerlessService)(nil).GetActivationCount), arg0)
+}
+
 // GetActivationLogs mocks base method.
 func (m *MockServerlessService) GetActivationLogs(arg0 string) (whisk.Activation, error) {
 	m.ctrl.T.Helper()
@@ -290,10 +305,10 @@ func (mr *MockServerlessServiceMockRecorder) InstallServerless(arg0, arg1 interf
 }
 
 // InvokeFunction mocks base method.
-func (m *MockServerlessService) InvokeFunction(arg0 string, arg1 interface{}, arg2, arg3 bool) (map[string]interface{}, error) {
+func (m *MockServerlessService) InvokeFunction(arg0 string, arg1 interface{}, arg2, arg3 bool) (interface{}, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "InvokeFunction", arg0, arg1, arg2, arg3)
-	ret0, _ := ret[0].(map[string]interface{})
+	ret0, _ := ret[0].(interface{})
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
@@ -305,7 +320,7 @@ func (mr *MockServerlessServiceMockRecorder) InvokeFunction(arg0, arg1, arg2, ar
 }
 
 // InvokeFunctionViaWeb mocks base method.
-func (m *MockServerlessService) InvokeFunctionViaWeb(arg0 string, arg1 map[string]interface{}) error {
+func (m *MockServerlessService) InvokeFunctionViaWeb(arg0 string, arg1 interface{}) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "InvokeFunctionViaWeb", arg0, arg1)
 	ret0, _ := ret[0].(error)
diff --git a/do/serverless.go b/do/serverless.go
index 6e2c07d9..f1c92124 100644
--- a/do/serverless.go
+++ b/do/serverless.go
@@ -219,9 +219,10 @@ type ServerlessService interface {
 	InstallServerless(string, bool) error
 	GetFunction(string, bool) (whisk.Action, []FunctionParameter, error)
 	ListFunctions(string, int, int) ([]whisk.Action, error)
-	InvokeFunction(string, interface{}, bool, bool) (map[string]interface{}, error)
-	InvokeFunctionViaWeb(string, map[string]interface{}) error
+	InvokeFunction(string, interface{}, bool, bool) (interface{}, error)
+	InvokeFunctionViaWeb(string, interface{}) error
 	ListActivations(whisk.ActivationListOptions) ([]whisk.Activation, error)
+	GetActivationCount(whisk.ActivationCountOptions) (whisk.ActivationCount, error)
 	GetActivation(string) (whisk.Activation, error)
 	GetActivationLogs(string) (whisk.Activation, error)
 	GetActivationResult(string) (whisk.Response, error)
@@ -746,7 +747,7 @@ func (s *serverlessService) ListFunctions(pkg string, skip int, limit int) ([]wh
 }
 
 // InvokeFunction invokes a function via POST with authentication
-func (s *serverlessService) InvokeFunction(name string, params interface{}, blocking bool, result bool) (map[string]interface{}, error) {
+func (s *serverlessService) InvokeFunction(name string, params interface{}, blocking bool, result bool) (interface{}, error) {
 	var empty map[string]interface{}
 	err := initWhisk(s)
 	if err != nil {
@@ -757,7 +758,7 @@ func (s *serverlessService) InvokeFunction(name string, params interface{}, bloc
 }
 
 // InvokeFunctionViaWeb invokes a function via GET using its web URL (or error if not a web function)
-func (s *serverlessService) InvokeFunctionViaWeb(name string, params map[string]interface{}) error {
+func (s *serverlessService) InvokeFunctionViaWeb(name string, params interface{}) error {
 	// Get the function so we can use its metadata in formulating the request
 	theFunction, _, err := s.GetFunction(name, false)
 	if err != nil {
@@ -789,7 +790,7 @@ func (s *serverlessService) InvokeFunctionViaWeb(name string, params map[string]
 	// Add params, if any
 	if params != nil {
 		encoded := url.Values{}
-		for key, val := range params {
+		for key, val := range params.(map[string]interface{}) {
 			stringVal, ok := val.(string)
 			if !ok {
 				return fmt.Errorf("the value of '%s' is not a string; web invocation is not possible", key)
@@ -812,6 +813,17 @@ func (s *serverlessService) ListActivations(options whisk.ActivationListOptions)
 	return resp, err
 }
 
+// GetActivationCount drives the OpenWhisk API for getting the total number of activations in namespace
+func (s *serverlessService) GetActivationCount(options whisk.ActivationCountOptions) (whisk.ActivationCount, error) {
+	err := initWhisk(s)
+	empty := whisk.ActivationCount{}
+	if err != nil {
+		return empty, err
+	}
+	resp, _, err := s.owClient.Activations.Count(&options)
+	return *resp, err
+}
+
 // GetActivation drives the OpenWhisk API getting an activation
 func (s *serverlessService) GetActivation(id string) (whisk.Activation, error) {
 	empty := whisk.Activation{}
diff --git a/go.mod b/go.mod
index 6779c045..f04b41df 100644
--- a/go.mod
+++ b/go.mod
@@ -46,8 +46,7 @@ require (
 
 require (
 	github.com/MakeNowJust/heredoc v1.0.0
-	github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b
-	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
+	github.com/apache/openwhisk-client-go v0.0.0-20221014112704-1ca897633f2d
 	github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
 	github.com/charmbracelet/bubbles v0.13.1-0.20220731172002-8f6516082803
 	github.com/charmbracelet/bubbletea v0.22.0
@@ -57,6 +56,7 @@ require (
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 	github.com/muesli/reflow v0.3.0
 	github.com/muesli/termenv v0.12.0
+	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
 	gopkg.in/yaml.v3 v3.0.1
 )
 
diff --git a/go.sum b/go.sum
index c86db254..37dea2e3 100644
--- a/go.sum
+++ b/go.sum
@@ -92,8 +92,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b h1:E0IprB8sveEHNP3WiZB/N/3nWzljyQCIDYFg0N3geGU=
-github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b/go.mod h1:SAQU4bHGJ0sg6c1vQ8ojmQKXgGaneVnexWX4+2/KMr8=
+github.com/apache/openwhisk-client-go v0.0.0-20221014112704-1ca897633f2d h1:8sh89OGDm1tx/D/nsFwunhX90NjEPTn2k/DDLhOjexs=
+github.com/apache/openwhisk-client-go v0.0.0-20221014112704-1ca897633f2d/go.mod h1:SAQU4bHGJ0sg6c1vQ8ojmQKXgGaneVnexWX4+2/KMr8=
 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
diff --git a/vendor/github.com/apache/openwhisk-client-go/whisk/action.go b/vendor/github.com/apache/openwhisk-client-go/whisk/action.go
index 53965683..36732360 100644
--- a/vendor/github.com/apache/openwhisk-client-go/whisk/action.go
+++ b/vendor/github.com/apache/openwhisk-client-go/whisk/action.go
@@ -287,8 +287,8 @@ func (s *ActionService) Delete(actionName string) (*http.Response, error) {
 	return resp, nil
 }
 
-func (s *ActionService) Invoke(actionName string, payload interface{}, blocking bool, result bool) (map[string]interface{}, *http.Response, error) {
-	var res map[string]interface{}
+func (s *ActionService) Invoke(actionName string, payload interface{}, blocking bool, result bool) (interface{}, *http.Response, error) {
+	var res interface{}
 
 	// Encode resource name as a path (with no query params) before inserting it into the URI
 	// This way any '?' chars in the name won't be treated as the beginning of the query params
diff --git a/vendor/github.com/apache/openwhisk-client-go/whisk/activation.go b/vendor/github.com/apache/openwhisk-client-go/whisk/activation.go
index fdbe40cd..2082bfa1 100644
--- a/vendor/github.com/apache/openwhisk-client-go/whisk/activation.go
+++ b/vendor/github.com/apache/openwhisk-client-go/whisk/activation.go
@@ -20,11 +20,12 @@ package whisk
 import (
 	"errors"
 	"fmt"
-	"github.com/apache/openwhisk-client-go/wski18n"
 	"net/http"
 	"net/url"
 	"strconv"
 	"time"
+
+	"github.com/apache/openwhisk-client-go/wski18n"
 )
 
 type ActivationService struct {
@@ -48,6 +49,10 @@ type Activation struct {
 	Publish      *bool       `json:"publish,omitempty"`
 }
 
+type ActivationCount struct {
+	Activations int64 `json:"activations"`
+}
+
 type ActivationFilteredRow struct {
 	Row       Activation
 	HeaderFmt string
@@ -61,7 +66,7 @@ type Response struct {
 	Result     *Result `json:"result,omitempty"`
 }
 
-type Result map[string]interface{}
+type Result interface{}
 
 type ActivationListOptions struct {
 	Name  string `url:"name,omitempty"`
@@ -72,7 +77,15 @@ type ActivationListOptions struct {
 	Docs  bool   `url:"docs,omitempty"`
 }
 
-//MWD - This structure may no longer be needed as the log format is now a string and not JSON
+type ActivationCountOptions struct {
+	Name  string `url:"name,omitempty"`
+	Skip  int    `url:"skip"`
+	Since int64  `url:"since,omitempty"`
+	Upto  int64  `url:"upto,omitempty"`
+	Count bool   `url:"count,omitempty"`
+}
+
+// MWD - This structure may no longer be needed as the log format is now a string and not JSON
 type Log struct {
 	Log    string `json:"log,omitempty"`
 	Stream string `json:"stream,omitempty"`
@@ -118,7 +131,9 @@ func TruncateStr(str string, maxlen int) string {
 }
 
 // ToSummaryRowString() returns a compound string of required parameters for printing
-//   from CLI command `wsk activation list`.
+//
+//	from CLI command `wsk activation list`.
+//
 // ***Method of type Sortable***
 func (activation ActivationFilteredRow) ToSummaryRowString() string {
 	s := time.Unix(0, activation.Row.Start*1000000)
@@ -189,6 +204,44 @@ func (s *ActivationService) List(options *ActivationListOptions) ([]Activation,
 	return activations, resp, nil
 }
 
+func (s *ActivationService) Count(options *ActivationCountOptions) (*ActivationCount, *http.Response, error) {
+	// TODO :: for some reason /activations only works with "_" as namespace
+	s.client.Namespace = "_"
+	route := "activations"
+
+	options.Count = true
+	routeUrl, err := addRouteOptions(route, options)
+
+	if err != nil {
+		Debug(DbgError, "addRouteOptions(%s, %#v) error: '%s'\n", route, options, err)
+		errStr := wski18n.T("Unable to append options '{{.options}}' to URL route '{{.route}}': {{.err}}",
+			map[string]interface{}{"options": fmt.Sprintf("%#v", options), "route": route, "err": err})
+		werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+		return nil, nil, werr
+	}
+
+	req, err := s.client.NewRequestUrl("GET", routeUrl, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired)
+	if err != nil {
+		Debug(DbgError, "http.NewRequestUrl(GET, %s, nil, IncludeNamespaceInUrl, AppendOpenWhiskPathPrefix, EncodeBodyAsJson, AuthRequired) error: '%s'\n", route, err)
+		errStr := wski18n.T("Unable to create HTTP request for GET '{{.route}}': {{.err}}",
+			map[string]interface{}{"route": route, "err": err})
+		werr := MakeWskErrorFromWskError(errors.New(errStr), err, EXIT_CODE_ERR_GENERAL, DISPLAY_MSG, NO_DISPLAY_USAGE)
+		return nil, nil, werr
+	}
+
+	Debug(DbgInfo, "Sending HTTP request - URL '%s'; req %#v\n", req.URL.String(), req)
+
+	count := new(ActivationCount)
+	resp, err := s.client.Do(req, &count, ExitWithSuccessOnTimeout)
+
+	if err != nil {
+		Debug(DbgError, "s.client.Do() error - HTTP req %s; error '%s'\n", req.URL.String(), err)
+		return nil, resp, err
+	}
+
+	return count, resp, nil
+}
+
 func (s *ActivationService) Get(activationID string) (*Activation, *http.Response, error) {
 	// TODO :: for some reason /activations/:id only works with "_" as namespace
 	s.client.Namespace = "_"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index cdb6c89d..3ae96533 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -33,7 +33,7 @@ github.com/Microsoft/hcsshim/internal/vmcompute
 github.com/Microsoft/hcsshim/internal/wclayer
 github.com/Microsoft/hcsshim/internal/winapi
 github.com/Microsoft/hcsshim/osversion
-# github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b
+# github.com/apache/openwhisk-client-go v0.0.0-20221014112704-1ca897633f2d
 ## explicit; go 1.15
 github.com/apache/openwhisk-client-go/whisk
 github.com/apache/openwhisk-client-go/wski18n


Test patch:
diff --git a/commands/activations_test.go b/commands/activations_test.go
index f463bdf5..4301980c 100644
--- a/commands/activations_test.go
+++ b/commands/activations_test.go
@@ -17,6 +17,7 @@ import (
 	"bytes"
 	"os/exec"
 	"sort"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -42,6 +43,18 @@ func TestActivationsCommand(t *testing.T) {
 	assert.Equal(t, expected, names)
 }
 
+var hello1Result = whisk.Result(map[string]interface{}{
+	"body": "Hello stranger!",
+})
+
+var hello2Result = whisk.Result(map[string]interface{}{
+	"body": "Hello Archie!",
+})
+
+var hello3Result = whisk.Result(map[string]interface{}{
+	"error": "Missing main/no code to execute.",
+})
+
 // theActivations is the set of activation assumed to be present, used to mock whisk API behavior
 var theActivations = []whisk.Activation{
 	{
@@ -55,9 +68,7 @@ var theActivations = []whisk.Activation{
 			Status:     "success",
 			StatusCode: 0,
 			Success:    true,
-			Result: &whisk.Result{
-				"body": "Hello stranger!",
-			},
+			Result:     &hello1Result,
 		},
 		Logs: []string{
 			"2022-09-30T11:53:50.567914279Z stdout: Hello stranger!",
@@ -74,9 +85,7 @@ var theActivations = []whisk.Activation{
 			Status:     "success",
 			StatusCode: 0,
 			Success:    true,
-			Result: &whisk.Result{
-				"body": "Hello Archie!",
-			},
+			Result:     &hello2Result,
 		},
 		Logs: []string{
 			"2022-09-30T11:53:50.567914279Z stdout: Hello Archie!",
@@ -90,15 +99,17 @@ var theActivations = []whisk.Activation{
 		Start:        1664538850000,
 		End:          1664538860000,
 		Response: whisk.Response{
-			Result: &whisk.Result{
-				"error": "Missing main/no code to execute.",
-			},
+			Result:  &hello3Result,
 			Status:  "developer error",
 			Success: false,
 		},
 	},
 }
 
+var theActivationCount = whisk.ActivationCount{
+	Activations: 1738,
+}
+
 // Timestamps in the activations are converted to dates using local time so, to make this test capable of running
 // in any timezone, we need to abstract things a bit.  Following the conventions in aio, the banner dates are computed
 // from End and the activation record dates from Start.
@@ -312,65 +323,68 @@ Hello stranger!
 
 func TestActivationsList(t *testing.T) {
 	tests := []struct {
-		name            string
-		doctlArgs       string
-		doctlFlags      map[string]string
-		expectedNimArgs []string
+		name       string
+		doctlArgs  string
+		doctlFlags map[string]string
 	}{
 		{
-			name:            "no flags or args",
-			expectedNimArgs: []string{},
+			name:      "no flags or args",
+			doctlArgs: "",
 		},
 		{
-			name:            "full flag",
-			doctlFlags:      map[string]string{"full": ""},
-			expectedNimArgs: []string{"--full"},
+			name:      "function name argument",
+			doctlArgs: "my-package/hello4",
 		},
 		{
-			name:            "count flag",
-			doctlFlags:      map[string]string{"count": ""},
-			expectedNimArgs: []string{"--count"},
+			name:       "count flag",
+			doctlArgs:  "",
+			doctlFlags: map[string]string{"count": "true", "limit": "10"},
 		},
 		{
-			name:            "limit flag",
-			doctlFlags:      map[string]string{"limit": "10"},
-			expectedNimArgs: []string{"--limit", "10"},
-		},
-		{
-			name:            "since flag",
-			doctlFlags:      map[string]string{"since": "1644866670085"},
-			expectedNimArgs: []string{"--since", "1644866670085"},
-		},
-		{
-			name:            "skip flag",
-			doctlFlags:      map[string]string{"skip": "1"},
-			expectedNimArgs: []string{"--skip", "1"},
-		},
-		{
-			name:            "upto flag",
-			doctlFlags:      map[string]string{"upto": "1644866670085"},
-			expectedNimArgs: []string{"--upto", "1644866670085"},
-		},
-		{
-			name:            "multiple flags",
-			doctlFlags:      map[string]string{"limit": "10", "count": ""},
-			expectedNimArgs: []string{"--count", "--limit", "10"},
+			name:       "multiple flags and arg",
+			doctlArgs:  "",
+			doctlFlags: map[string]string{"limit": "10", "skip": "100", "since": "1664538750000", "upto": "1664538850000"},
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
-				fakeCmd := &exec.Cmd{
-					Stdout: config.Out,
-				}
+				buf := &bytes.Buffer{}
+				config.Out = buf
 
 				if tt.doctlArgs != "" {
 					config.Args = append(config.Args, tt.doctlArgs)
 				}
 
+				count := false
+				var limit interface{}
+				var since interface{}
+				var upto interface{}
+				var skip interface{}
+
 				if tt.doctlFlags != nil {
 					for k, v := range tt.doctlFlags {
+						if k == "count" {
+							count = true
+						}
+
+						if k == "limit" {
+							limit, _ = strconv.ParseInt(v, 0, 64)
+						}
+
+						if k == "since" {
+							since, _ = strconv.ParseInt(v, 0, 64)
+						}
+
+						if k == "upto" {
+							upto, _ = strconv.ParseInt(v, 0, 64)
+						}
+
+						if k == "skip" {
+							skip, _ = strconv.ParseInt(v, 0, 64)
+						}
+
 						if v == "" {
 							config.Doit.Set(config.NS, k, true)
 						} else {
@@ -379,9 +393,39 @@ func TestActivationsList(t *testing.T) {
 					}
 				}
 
-				tm.serverless.EXPECT().CheckServerlessStatus(hashAccessToken(config)).MinTimes(1).Return(nil)
-				tm.serverless.EXPECT().Cmd("activation/list", tt.expectedNimArgs).Return(fakeCmd, nil)
-				tm.serverless.EXPECT().Exec(fakeCmd).Return(do.ServerlessOutput{}, nil)
+				if count {
+					expectedListOptions := whisk.ActivationCountOptions{}
+					if since != nil {
+						expectedListOptions.Since = since.(int64)
+					}
+
+					if upto != nil {
+						expectedListOptions.Upto = upto.(int64)
+					}
+
+					tm.serverless.EXPECT().GetActivationCount(expectedListOptions).Return(theActivationCount, nil)
+				} else {
+					expectedListOptions := whisk.ActivationListOptions{}
+					if since != nil {
+						expectedListOptions.Since = since.(int64)
+					}
+
+					if upto != nil {
+						expectedListOptions.Upto = upto.(int64)
+					}
+
+					if len(config.Args) == 1 {
+						expectedListOptions.Name = config.Args[0]
+					}
+					if limit != nil {
+						expectedListOptions.Limit = int(limit.(int64))
+					}
+
+					if skip != nil {
+						expectedListOptions.Skip = int(skip.(int64))
+					}
+					tm.serverless.EXPECT().ListActivations(expectedListOptions).Return(theActivations, nil)
+				}
 
 				err := RunActivationsList(config)
 				require.NoError(t, err)