Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive namespace selection #1285

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
11ac703
Add support for typescript in the nodejs runtime (#1225)
joshuaauerbachwatson Sep 7, 2022
a75e480
Eliminate plugin usage for sls fn invoke (#1226)
joshuaauerbachwatson Sep 12, 2022
fb0ffea
Merge branch 'main' into reverse-merge-main
joshuaauerbachwatson Sep 20, 2022
a8e75d1
Merge pull request #1239 from joshuaauerbachwatson/reverse-merge-main
joshuaauerbachwatson Sep 20, 2022
908c9ce
Add doctl serverless trigger support (for scheduled functions) (#1232)
joshuaauerbachwatson Sep 27, 2022
22fc885
Merge branch 'main' into latest-main
joshuaauerbachwatson Sep 27, 2022
083868e
Merge pull request #1253 from joshuaauerbachwatson/latest-main
joshuaauerbachwatson Sep 27, 2022
068350a
Merge branch 'main' into feature/serverless
andrewsomething Sep 27, 2022
7ca75fc
Eliminate plugin usage in 'doctl sls fn list'
joshuaauerbachwatson Sep 8, 2022
0c5d710
Hidden flags enabling connection to dev clusters
joshuaauerbachwatson Sep 12, 2022
c5a2361
Fix unit test (date handling is timezone specific)
joshuaauerbachwatson Sep 26, 2022
343ac51
Eliminate call to auth/current via the plugin
joshuaauerbachwatson Sep 26, 2022
8a26142
Commit changed test (screwed it up last time)
joshuaauerbachwatson Sep 26, 2022
407f28a
Remove accidental re-introduction of --beta flag
joshuaauerbachwatson Sep 28, 2022
bd979f2
Bump min nim version to incorporate small bug fix
joshuaauerbachwatson Sep 29, 2022
f2687c4
Merge pull request #1252 from joshuaauerbachwatson/function-list-no-p…
joshuaauerbachwatson Sep 29, 2022
64028da
Avoid plugin in serverless activations [ get | result ] (#1270)
joshuaauerbachwatson Oct 11, 2022
5b22684
Updates activation list command to use the whisk client instead of th…
ddebarros Oct 11, 2022
142fe8d
Adds name filter to activations list
ddebarros Oct 11, 2022
b51dccc
moved getActivationStatus to utils
ddebarros Oct 11, 2022
f3e3ebd
re-generates mocks
ddebarros Oct 11, 2022
046ee83
made changes from PR review and updated the unit tests
ddebarros Oct 12, 2022
72bfb84
Updates test
ddebarros Oct 12, 2022
37f36ad
default to json output when the full flag is set
ddebarros Oct 12, 2022
ecd13f1
list command takes a function name not activation name
ddebarros Oct 12, 2022
a04b853
Updates the apache-go-client library and updates the tests to reflect…
ddebarros Oct 14, 2022
d4abffc
Makes updates from PR review
ddebarros Oct 14, 2022
ca6267a
Merge pull request #1277 from ddebarros/add-activations-list
ddebarros Oct 14, 2022
caa607b
Uses bubbles list to display an interactive namespace selection list
ddebarros Oct 17, 2022
2b761a8
Uses bubbles list to display an interactive namespace selection list
ddebarros Oct 17, 2022
88f1753
Adds a new spinner re-usable component
ddebarros Oct 18, 2022
5acfef5
Updates tests
ddebarros Oct 18, 2022
056c480
makes spinner message private
ddebarros Oct 20, 2022
e98354d
Updates command to respect the interactive flag
ddebarros Oct 20, 2022
d25febf
Merge remote-tracking branch 'origin' into interactive-namespace-sele…
ddebarros Oct 27, 2022
f8ac4ca
Merge branch 'main' of https://github.com/digitalocean/doctl into int…
ddebarros Nov 1, 2022
9a9c47d
Updates interactive check
ddebarros Nov 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 238 additions & 18 deletions commands/activations.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,26 @@ limitations under the License.
package commands

import (
"encoding/json"
"fmt"
"io"
"regexp"
"time"

"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"
)

// ShownActivation is what is actually shown as an activation ... it adds a date field which is a human-readable
// version of the start field.
type ShownActivation struct {
whisk.Activation
Date string `json:"date,omitempty"`
}

// Activations generates the serverless 'activations' subtree for addition to the doctl command
func Activations() *Command {
cmd := &Command{
Expand All @@ -43,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")

Expand Down Expand Up @@ -85,25 +103,186 @@ func RunActivationsGet(c *CmdConfig) error {
if argCount > 1 {
return doctl.NewTooManyArgsErr(c.NS)
}
replaceFunctionWithAction(c)
output, err := RunServerlessExec(activationGet, c, []string{flagLast, flagLogs, flagResult, flagQuiet}, []string{flagSkip, flagAction})
if err != nil {
return err
var id string
if argCount > 0 {
id = c.Args[0]
}
return c.PrintServerlessTextOutput(output)
logsFlag, _ := c.Doit.GetBool(c.NS, flagLogs)
resultFlag, _ := c.Doit.GetBool(c.NS, flagResult)
quietFlag, _ := c.Doit.GetBool(c.NS, flagQuiet)
// There is also a 'last' flag, which is historical. Since it's behavior is the
// default, and the past convention was to ignore it if a single id was specified,
// (rather than indicating an error), it is completely ignored here but accepted for
// backward compatibility. In the aio implementation (incorporated in nim, previously
// incorporated here), the flag had to be set explicitly (rather than just implied) in
// order to get a "banner" (additional informational line) when requesting logs or
// result only. This seems pointless and we will always display the banner for a
// single logs or result output unless --quiet is specified.
skipFlag, _ := c.Doit.GetInt(c.NS, flagSkip) // 0 if not there
functionFlag, _ := c.Doit.GetString(c.NS, flagFunction)
sls := c.Serverless()
if id == "" {
// If there is no id, the convention is to retrieve the last activation, subject to possible
// filtering or skipping
options := whisk.ActivationListOptions{Limit: 1, Skip: skipFlag}
if functionFlag != "" {
options.Name = functionFlag
}
list, err := sls.ListActivations(options)
if err != nil {
return err
}
if len(list) == 0 {
return fmt.Errorf("no activations were returned")
}
activation := list[0]
id = activation.ActivationID
if !quietFlag && (logsFlag || resultFlag) {
makeBanner(c.Out, activation)
}
}
if logsFlag {
activation, err := sls.GetActivationLogs(id)
if err != nil {
return err
}
if len(activation.Logs) == 0 {
return fmt.Errorf("no logs available")
}
printLogs(c.Out, true, activation)
} else if resultFlag {
response, err := sls.GetActivationResult(id)
if err != nil {
return err
}
if response.Result == nil {
return fmt.Errorf("no result available")
}
printResult(c.Out, response.Result)
} else {
activation, err := sls.GetActivation(id)
if err != nil {
return err
}
printActivationRecord(c.Out, activation)
}
return nil
}

// makeBanner is a subroutine that prints a single "banner" line summarizing information about an
// activation. This is done in conjunction with a request to print only logs or only the result, since,
// otherwise, it is difficult to know what activation is being talked about.
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, displayers.GetActivationStatus(activation.StatusCode),
end, activation.Name, activation.Version)
msg := text.NewStyled(body).Highlight()
fmt.Fprintln(writer, init.String()+msg.String())
}

// 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 {
if strip {
log = stripLog(log)
}
fmt.Fprintln(writer, log)
}
}

// dtsRegex is a regular expression that matches the prefix of some activation log entries.
// It is used by stripLog to remove that prefix
var dtsRegex = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:.*: `)

// stripLog strips the prefix from log entries
func stripLog(entry string) string {
// `2019-10-11T19:08:57.298Z stdout: login-success :: { code: ...`
// should become: `login-success :: { code: ...`
found := dtsRegex.FindString(entry)
return entry[len(found):]
}

// printResult is a subroutine for printing just the result of an activation
func printResult(writer io.Writer, result *whisk.Result) {
var msg string
bytes, err := json.MarshalIndent(result, "", " ")
if err == nil {
msg = string(bytes)
} else {
msg = "<unable to represent the result as JSON>"
}
fmt.Fprintln(writer, msg)
}

// printActivationRecord is a subroutine for printing the entire activation record
func printActivationRecord(writer io.Writer, activation whisk.Activation) {
var msg string
date := time.UnixMilli(activation.Start).Format("2006-01-02 03:04:05")
toShow := ShownActivation{Activation: activation, Date: date}
bytes, err := json.MarshalIndent(toShow, "", " ")
if err == nil {
msg = string(bytes)
} else {
msg = "<unable to represent the activation as JSON>"
}
fmt.Fprintln(writer, msg)
}

// 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
Expand Down Expand Up @@ -142,12 +321,53 @@ func RunActivationsResult(c *CmdConfig) error {
if argCount > 1 {
return doctl.NewTooManyArgsErr(c.NS)
}
replaceFunctionWithAction(c)
output, err := RunServerlessExec(activationResult, c, []string{flagLast, flagQuiet}, []string{flagLimit, flagSkip, flagAction})
if err != nil {
return err
var id string
if argCount > 0 {
id = c.Args[0]
}
return c.PrintServerlessTextOutput(output)
quietFlag, _ := c.Doit.GetBool(c.NS, flagQuiet)
skipFlag, _ := c.Doit.GetInt(c.NS, flagSkip) // 0 if not there
limitFlag, _ := c.Doit.GetInt(c.NS, flagLimit) // 0 if not there
functionFlag, _ := c.Doit.GetString(c.NS, flagFunction)
limit := 1
if limitFlag > 200 {
limit = 200
} else if limitFlag > 0 {
limit = limitFlag
}
options := whisk.ActivationListOptions{Limit: limit, Skip: skipFlag}
sls := c.Serverless()
var activations []whisk.Activation
if id == "" {
if functionFlag != "" {
options.Name = functionFlag
}
actv, err := sls.ListActivations(options)
if err != nil {
return err
}
activations = actv
} else {
activations = []whisk.Activation{
{ActivationID: id},
}
}
reversed := make([]whisk.Activation, len(activations))
for i, activation := range activations {
response, err := sls.GetActivationResult(activation.ActivationID)
if err != nil {
return err
}
activation.Result = response.Result
reversed[len(activations)-i-1] = activation
}
for _, activation := range reversed {
if !quietFlag && id == "" {
makeBanner(c.Out, activation)
}
printResult(c.Out, activation.Result)
}
return nil
}

// replaceFunctionWithAction detects that --function was specified and renames it to --action (which is what nim
Expand Down
Loading