Skip to content

Commit

Permalink
Merge current feature/serverless contents preparatory to a release (#…
Browse files Browse the repository at this point in the history
…1186)

* Rename sandbox to serverless + drop sandbox term (#1178)

In order to drop the term "sandbox" the help texts now use the terms
"functions project" (for the disk area) and "functions namespace".

* Change 'actions' to 'functions' in externals where feasible (#1177)

* Replace 'actions' with 'functions' in externals

1. Re-do 'doctl sis fn list' output processing.
   - revised header and columns
2. Increment nim version, which will
   a. Cause 'functions' to be accepted in project.yml
   b. Use 'functions' in generated project.yml.
   c. Use 'functions' in output of get-metadata.

* Adopt suggestions from PR review

(1) The displayer is now properly attached to the command so that the
--format flag will work.  (2) The --count flag is mutually exclusive
with the format flag, since it will bypass the displayer.

* Set a unique user agent string for doctl sls (#1183)

Previously, the use of doctl sls could not be distinguished from the
use of 'nim' CLI.

* Store serverless credentials directly instead of invoking the plugin with auth/login (#1184)

* Eliminate call to nim auth login

Replaced by a pure file system implementation that achieves the same
result.

* Tight file permissions on credentials

* Fix test failures, clean up a few comments

* Fix problem with first connect after install

* Fix transcription error
  • Loading branch information
joshuaauerbachwatson authored Jun 30, 2022
1 parent f285080 commit 8dc2afa
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 136 deletions.
14 changes: 7 additions & 7 deletions commands/activations.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ func Activations() *Command {
Command: &cobra.Command{
Use: "activations",
Short: "Work with activation records",
Long: `The subcommands of ` + "`" + `doctl sandbox activations` + "`" + ` will list or retrieve results, logs, or complete
"activation records" which result from invoking functions deployed to your sandbox.`,
Long: `The subcommands of ` + "`" + `doctl serverless activations` + "`" + ` will list or retrieve results, logs, or complete
"activation records" which result from invoking functions deployed to your functions namespace.`,
Aliases: []string{"actv"},
},
}

get := CmdBuilder(cmd, RunActivationsGet, "get [<activationId>]", "Retrieves an Activation",
`Use `+"`"+`doctl sandbox activations get`+"`"+` to retrieve the activation record for a previously invoked function.
`Use `+"`"+`doctl serverless activations get`+"`"+` to retrieve the activation record for a previously invoked function.
There are several options for specifying the activation you want. You can limit output to the result
or the logs. The `+"`"+`doctl sandbox activation logs`+"`"+` command has additional advanced capabilities for retrieving
or the logs. The `+"`"+`doctl serverless activation logs`+"`"+` command has additional advanced capabilities for retrieving
logs.`,
Writer)
AddBoolFlag(get, "last", "l", false, "Fetch the most recent activation (default)")
Expand All @@ -44,7 +44,7 @@ logs.`,
AddBoolFlag(get, "quiet", "q", false, "Suppress last activation information header")

list := CmdBuilder(cmd, RunActivationsList, "list [<activation_name>]", "Lists Activations for which records exist",
`Use `+"`"+`doctl sandbox activations list`+"`"+` to list the activation records that are present in the cloud for previously
`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)")
Expand All @@ -55,7 +55,7 @@ invoked functions.`,
AddBoolFlag(list, "full", "f", false, "include full activation description")

logs := CmdBuilder(cmd, RunActivationsLogs, "logs [<activationId>]", "Retrieves the Logs for an Activation",
`Use `+"`"+`doctl sandbox activations logs`+"`"+` to retrieve the logs portion of one or more activation records
`Use `+"`"+`doctl serverless activations logs`+"`"+` to retrieve the logs portion of one or more activation records
with various options, such as selecting by package or function, and optionally watching continuously
for new arrivals.`,
Writer)
Expand All @@ -67,7 +67,7 @@ for new arrivals.`,
AddBoolFlag(logs, "follow", "", false, "Fetch logs continuously")

result := CmdBuilder(cmd, RunActivationsResult, "result [<activationId>]", "Retrieves the Results for an Activation",
`Use `+"`"+`doctl sandbox activations result`+"`"+` to retrieve just the results portion
`Use `+"`"+`doctl serverless activations result`+"`"+` to retrieve just the results portion
of one or more activation records.`,
Writer)
AddBoolFlag(result, "last", "l", false, "Fetch the most recent activation result (default)")
Expand Down
5 changes: 4 additions & 1 deletion commands/command_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init
node := filepath.Join(sandboxDir, nodeBin)
sandboxJs := filepath.Join(sandboxDir, "sandbox.js")
nimbellaDir := getCredentialDirectory(c, sandboxDir)
c.Sandbox = func() do.SandboxService { return do.NewSandboxService(sandboxJs, nimbellaDir, node, godoClient) }
userAgent := fmt.Sprintf("doctl/%s serverless/%s", doctl.DoitVersion.String(), minSandboxVersion)
c.Sandbox = func() do.SandboxService {
return do.NewSandboxService(sandboxJs, nimbellaDir, node, userAgent, godoClient)
}

return nil
},
Expand Down
87 changes: 87 additions & 0 deletions commands/displayers/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright 2018 The Doctl Authors All rights reserved.
Licensed 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 displayers

import (
"io"
"strings"
"time"

"github.com/digitalocean/doctl/do"
)

// Functions is the type of the displayer for functions list
type Functions struct {
Info []do.FunctionInfo
}

var _ Displayable = &Functions{}

// JSON is the displayer JSON method specialized for functions list
func (i *Functions) JSON(out io.Writer) error {
return writeJSON(i.Info, out)
}

// Cols is the displayer Cols method specialized for functions list
func (i *Functions) Cols() []string {
return []string{
"Update", "Version", "Runtime", "Function",
}
}

// ColMap is the displayer ColMap method specialized for functions list
func (i *Functions) ColMap() map[string]string {
return map[string]string{
"Update": "Latest Update",
"Runtime": "Runtime Kind",
"Version": "Latest Version",
"Function": "Function Name",
}
}

// KV is the displayer KV method specialized for functions list
func (i *Functions) KV() []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(i.Info))
for _, ii := range i.Info {
x := map[string]interface{}{
"Update": time.UnixMilli(ii.Updated).Format("01/02 03:04:05"),
"Runtime": findRuntime(ii.Annotations),
"Version": ii.Version,
"Function": computeFunctionName(ii.Name, ii.Namespace),
}
out = append(out, x)
}

return out
}

// findRuntime finds the runtime string amongst the annotations of a function
func findRuntime(annots []do.Annotation) string {
for i := range annots {
if annots[i].Key == "exec" {
return annots[i].Value.(string)
}
}
return "<unknown>"
}

// computeFunctionName computes the effective name of a function from its simple name and the 'namespace' field
// (which actually encodes both namespace and package).
func computeFunctionName(simpleName string, namespace string) string {
nsparts := strings.Split(namespace, "/")
if len(nsparts) > 1 {
return nsparts[1] + "/" + simpleName
}
return simpleName
}
49 changes: 37 additions & 12 deletions commands/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ limitations under the License.
package commands

import (
"encoding/json"
"errors"
"strings"

"github.com/digitalocean/doctl"
"github.com/digitalocean/doctl/commands/displayers"
"github.com/digitalocean/doctl/do"
"github.com/spf13/cobra"
)

Expand All @@ -26,17 +29,17 @@ func Functions() *Command {
cmd := &Command{
Command: &cobra.Command{
Use: "functions",
Short: "Work with the functions of your sandbox",
Long: `The subcommands of ` + "`" + `doctl sandbox functions` + "`" + ` operate on the deployed (cloud-resident) functions of your sandbox.
Short: "Work with the functions in your namespace",
Long: `The subcommands of ` + "`" + `doctl serverless functions` + "`" + ` operate on your functions namespace.
You are able to inspect and list these functions to know what is deployed. You can also invoke functions to test them.`,
Aliases: []string{"fn"},
},
}

get := CmdBuilder(cmd, RunFunctionsGet, "get <functionName>", "Retrieves the deployed copy of a function (code or metadata)",
`Use `+"`"+`doctl sandbox functions get`+"`"+` to obtain the code or metadata of a deployed function.
`Use `+"`"+`doctl serverless functions get`+"`"+` to obtain the code or metadata of a deployed function.
This allows you to inspect the deployed copy and ascertain whether it corresponds to what
is in your sandbox area in the local file system.`,
is in your functions project in the local file system.`,
Writer)
AddBoolFlag(get, "url", "r", false, "get function url")
AddBoolFlag(get, "code", "", false, "show function code (only works if code is not a zip file)")
Expand All @@ -46,19 +49,18 @@ is in your sandbox area in the local file system.`,
AddStringFlag(get, "save-as", "", "", "file to save function code to")

invoke := CmdBuilder(cmd, RunFunctionsInvoke, "invoke <functionName>", "Invokes a function",
`Use `+"`"+`doctl sandbox functions invoke`+"`"+` to invoke a function of your sandbox that has been deployed
to the cloud. You can provide inputs and inspect outputs.`,
`Use `+"`"+`doctl serverless functions invoke`+"`"+` to invoke a function in your functions namespace.
You can provide inputs and inspect outputs.`,
Writer)
AddBoolFlag(invoke, "web", "", false, "Invoke as a web function, show result as web page")
AddStringSliceFlag(invoke, "param", "p", []string{}, "parameter values in KEY:VALUE format, list allowed")
AddStringFlag(invoke, "param-file", "P", "", "FILE containing parameter values in JSON format")
AddBoolFlag(invoke, "full", "f", false, "wait for full activation record")
AddBoolFlag(invoke, "no-wait", "n", false, "fire and forget (asynchronous invoke, does not wait for the result)")

list := CmdBuilder(cmd, RunFunctionsList, "list [<packageName>]", "Lists all the functions",
`Use `+"`"+`doctl sandbox functions list`+"`"+` to list the functions of your sandbox that are deployed
to the cloud.`,
Writer)
list := CmdBuilder(cmd, RunFunctionsList, "list [<packageName>]", "Lists the functions in your functions namespace",
`Use `+"`"+`doctl serverless functions list`+"`"+` to list the functions in your functions namespace.`,
Writer, displayerType(&displayers.Functions{}))
AddStringFlag(list, "limit", "l", "", "only return LIMIT number of functions (default 30, max 200)")
AddStringFlag(list, "skip", "s", "", "exclude the first SKIP number of functions from the result")
AddBoolFlag(list, "count", "", false, "show only the total number of functions")
Expand Down Expand Up @@ -108,11 +110,34 @@ func RunFunctionsList(c *CmdConfig) error {
if argCount > 1 {
return doctl.NewTooManyArgsErr(c.NS)
}
output, err := RunSandboxExec(actionList, c, []string{flagCount, flagNameSort, flagNameName}, []string{flagLimit, flagSkip})
// Determine if '--count' is requested since we will use simple text output in that case.
// Count is mutually exclusive with the global format flag.
count, _ := c.Doit.GetBool(c.NS, flagCount)
if count && c.Doit.IsSet("format") {
return errors.New("the --count and --format flags are mutually exclusive")
}
// Add JSON flag so we can control output format
if !count {
c.Doit.Set(c.NS, flagJSON, true)
}
output, err := RunSandboxExec(actionList, c, []string{flagCount, flagNameSort, flagNameName, flagJSON}, []string{flagLimit, flagSkip})
if err != nil {
return err
}
return c.PrintSandboxTextOutput(output)
if count {
return c.PrintSandboxTextOutput(output)
}
// Reparse the output to use a more specific type, which can then be passed to the displayer
rawOutput, err := json.Marshal(output.Entity)
if err != nil {
return err
}
var formatted []do.FunctionInfo
err = json.Unmarshal(rawOutput, &formatted)
if err != nil {
return err
}
return c.Display(&displayers.Functions{Info: formatted})
}

// appendParams determines if there is a 'param' flag (value is a slice, elements
Expand Down
10 changes: 5 additions & 5 deletions commands/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func TestFunctionsList(t *testing.T) {
}{
{
name: "no flags or args",
expectedNimArgs: []string{},
expectedNimArgs: []string{"--json"},
},
{
name: "count flag",
Expand All @@ -210,22 +210,22 @@ func TestFunctionsList(t *testing.T) {
{
name: "limit flag",
doctlFlags: map[string]string{"limit": "1"},
expectedNimArgs: []string{"--limit", "1"},
expectedNimArgs: []string{"--json", "--limit", "1"},
},
{
name: "name flag",
doctlFlags: map[string]string{"name": ""},
expectedNimArgs: []string{"--name"},
expectedNimArgs: []string{"--name", "--json"},
},
{
name: "name-sort flag",
doctlFlags: map[string]string{"name-sort": ""},
expectedNimArgs: []string{"--name-sort"},
expectedNimArgs: []string{"--name-sort", "--json"},
},
{
name: "skip flag",
doctlFlags: map[string]string{"skip": "1"},
expectedNimArgs: []string{"--skip", "1"},
expectedNimArgs: []string{"--json", "--skip", "1"},
},
}

Expand Down
36 changes: 18 additions & 18 deletions commands/sandbox-extra.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ import (
// oclif equivalents and subsequently modified.
func SandboxExtras(cmd *Command) {

create := CmdBuilder(cmd, RunSandboxExtraCreate, "init <path>", "Initialize a local file system directory for the sandbox",
`The `+"`"+`doctl sandbox init`+"`"+` command specifies a directory in your file system which will hold functions and
supporting artifacts while you're developing them. When ready, you can upload these to the cloud for testing.
Later, after the area is committed to a `+"`"+`git`+"`"+` repository, you can create an app from them.
create := CmdBuilder(cmd, RunSandboxExtraCreate, "init <path>", "Initialize a 'functions project' directory in your local file system",
`The `+"`"+`doctl serverless init`+"`"+` command specifies a directory in your file system which will hold functions and
supporting artifacts while you're developing them. This 'functions project' can be uploaded to your functions namespace for testing.
Later, after the functions project is committed to a `+"`"+`git`+"`"+` repository, you can create an app, or an app component, from it.
Type `+"`"+`doctl sandbox status --languages`+"`"+` for a list of supported languages. Use one of the displayed keywords
to choose your sample language for `+"`"+`doctl sandbox init`+"`"+`.`,
Type `+"`"+`doctl serverless status --languages`+"`"+` for a list of supported languages. Use one of the displayed keywords
to choose your sample language for `+"`"+`doctl serverless init`+"`"+`.`,
Writer)
AddStringFlag(create, "language", "l", "javascript", "Language for the initial sample code")
AddBoolFlag(create, "overwrite", "", false, "Clears and reuses an existing directory")

deploy := CmdBuilder(cmd, RunSandboxExtraDeploy, "deploy <directories>", "Deploy sandbox local assets to the cloud",
`At any time you can use `+"`"+`doctl sandbox deploy`+"`"+` to upload the contents of a directory in your file system for
testing in the cloud. The area must be organized in the fashion expected by an App Platform Functions
component. The `+"`"+`doctl sandbox init`+"`"+` command will create a properly organized directory for you to work in.`,
deploy := CmdBuilder(cmd, RunSandboxExtraDeploy, "deploy <directories>", "Deploy a functions project to your functions namespace",
`At any time you can use `+"`"+`doctl serverless deploy`+"`"+` to upload the contents of a functions project in your file system for
testing in your serverless namespace. The project must be organized in the fashion expected by an App Platform Functions
component. The `+"`"+`doctl serverless init`+"`"+` command will create a properly organized directory for you to work in.`,
Writer)
AddStringFlag(deploy, "env", "", "", "Path to runtime environment file")
AddStringFlag(deploy, "build-env", "", "", "Path to build-time environment file")
Expand All @@ -52,17 +52,17 @@ component. The `+"`"+`doctl sandbox init`+"`"+` command will create a properly
AddBoolFlag(deploy, "remote-build", "", false, "Run builds remotely")
AddBoolFlag(deploy, "incremental", "", false, "Deploy only changes since last deploy")

getMetadata := CmdBuilder(cmd, RunSandboxExtraGetMetadata, "get-metadata <directory>", "Obtain metadata of a sandbox directory",
`The `+"`"+`doctl sandbox get-metadata`+"`"+` command produces a JSON structure that summarizes the contents of a directory
you have designated for functions development. This can be useful for feeding into other tools.`,
getMetadata := CmdBuilder(cmd, RunSandboxExtraGetMetadata, "get-metadata <directory>", "Obtain metadata of a functions project",
`The `+"`"+`doctl serverless get-metadata`+"`"+` command produces a JSON structure that summarizes the contents of a functions
project (a directory you have designated for functions development). This can be useful for feeding into other tools.`,
Writer)
AddStringFlag(getMetadata, "env", "", "", "Path to environment file")
AddStringFlag(getMetadata, "include", "", "", "Functions or packages to include")
AddStringFlag(getMetadata, "exclude", "", "", "Functions or packages to exclude")

watch := CmdBuilder(cmd, RunSandboxExtraWatch, "watch <directory>", "Watch a sandbox directory, deploying incrementally on change",
watch := CmdBuilder(cmd, RunSandboxExtraWatch, "watch <directory>", "Watch a functions project directory, deploying incrementally on change",
`Type `+"`"+`doctl sandbox watch <directory>`+"`"+` in a separate terminal window. It will run until interrupted.
It will watch the directory (which should be one you initialized for sandbox use) and will deploy
It will watch the directory (which should be one you initialized for serverless development) and will deploy
the contents to the cloud incrementally as it detects changes.`,
Writer)
AddStringFlag(watch, "env", "", "", "Path to runtime environment file")
Expand Down Expand Up @@ -98,16 +98,16 @@ func RunSandboxExtraCreate(c *CmdConfig) error {
// is not quite right for doctl.
if jsonOutput, ok := output.Entity.(map[string]interface{}); ok {
if created, ok := jsonOutput["project"].(string); ok {
fmt.Fprintf(c.Out, `A local sandbox area '%s' was created for you.
fmt.Fprintf(c.Out, `A local functions project directory '%s' was created for you.
You may deploy it by running the command shown on the next line:
doctl sandbox deploy %s
doctl serverless deploy %s
`, created, created)
fmt.Fprintln(c.Out)
return nil
}
}
// Fall back if output is not structured the way we expect
fmt.Println("Sandbox initialized successfully in the local file system")
fmt.Println("Functions project initialized successfully in the local file system")
return nil
}

Expand Down
Loading

0 comments on commit 8dc2afa

Please sign in to comment.