Skip to content

Commit 6124eb7

Browse files
Improve friendliness of the languages table (#1160)
Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>
1 parent d0b5895 commit 6124eb7

File tree

5 files changed

+155
-14
lines changed

5 files changed

+155
-14
lines changed

commands/sandbox-extra.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ func SandboxExtras(cmd *Command) {
2525

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

commands/sandbox.go

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,27 @@ var (
6363
ErrSandboxNeedsUpgrade = errors.New("The sandbox support needs to be upgraded (use `doctl sandbox upgrade`)")
6464
// ErrSandboxNotConnected is the error returned to users when the sandbox is not connected to a namespace
6565
ErrSandboxNotConnected = errors.New("A sandbox is installed but not connected to a function namespace (use `doctl sandbox connect`)")
66-
// ErrUndeployAllAndArgs is the error returned when the --all flag is used along with args on undeploy
66+
// errUndeployAllAndArgs is the error returned when the --all flag is used along with args on undeploy
6767
errUndeployAllAndArgs = errors.New("command line arguments and the `--all` flag are mutually exclusive")
68-
// ErrUndeployTooFewArgs is the error returned when neither --all nor args are specified on undeploy
68+
// errUndeployTooFewArgs is the error returned when neither --all nor args are specified on undeploy
6969
errUndeployTooFewArgs = errors.New("either command line arguments or `--all` must be specified")
70+
71+
// languageKeywords maps the backend's runtime category names to keywords accepted as languages
72+
// Note: this table has all languages for which we possess samples. Only those with currently
73+
// active runtimes will display.
74+
languageKeywords map[string][]string = map[string][]string{
75+
"nodejs": {"javascript", "js"},
76+
"deno": {"deno"},
77+
"go": {"go", "golang"},
78+
"java": {"java"},
79+
"php": {"php"},
80+
"python": {"python", "py"},
81+
"ruby": {"ruby"},
82+
"rust": {"rust"},
83+
"swift": {"swift"},
84+
"dotnet": {"csharp", "cs"},
85+
"typescript": {"typescript", "ts"},
86+
}
7087
)
7188

7289
// Sandbox contains support for 'sandbox' commands provided by a hidden install of the Nimbella CLI
@@ -226,14 +243,38 @@ func RunSandboxStatus(c *CmdConfig) error {
226243
return errors.New("Could not retrieve information about the connected namespace")
227244
}
228245
mapResult := result.Entity.(map[string]interface{})
229-
fmt.Fprintf(c.Out, "Connected to function namespace '%s' on API host '%s'\n", mapResult["name"], mapResult["apihost"])
246+
apiHost := mapResult["apihost"].(string)
247+
fmt.Fprintf(c.Out, "Connected to function namespace '%s' on API host '%s'\n", mapResult["name"], apiHost)
230248
fmt.Fprintf(c.Out, "Sandbox version is %s\n\n", minSandboxVersion)
231-
displayRuntimes, _ := c.Doit.GetBool(c.NS, "languages")
232-
if displayRuntimes {
233-
result, err = SandboxExec(c, "info", "--runtimes")
234-
if result.Error == "" && err == nil {
235-
fmt.Fprintf(c.Out, "Available runtimes:\n")
236-
c.PrintSandboxTextOutput(result)
249+
languages, _ := c.Doit.GetBool(c.NS, "languages")
250+
if languages {
251+
return showLanguageInfo(c, apiHost)
252+
}
253+
return nil
254+
}
255+
256+
// showLanguageInfo is called by RunSandboxStatus when --languages is specified
257+
func showLanguageInfo(c *CmdConfig, APIHost string) error {
258+
info, err := c.Sandbox().GetHostInfo(APIHost)
259+
if err != nil {
260+
return err
261+
}
262+
fmt.Fprintf(c.Out, "Supported Languages:\n")
263+
for language := range info.Runtimes {
264+
fmt.Fprintf(c.Out, "%s:\n", language)
265+
keywords := strings.Join(languageKeywords[language], ", ")
266+
fmt.Fprintf(c.Out, " Keywords: %s\n", keywords)
267+
fmt.Fprintf(c.Out, " Runtime versions:\n")
268+
runtimes := info.Runtimes[language]
269+
for _, runtime := range runtimes {
270+
tag := ""
271+
if runtime.Default {
272+
tag = fmt.Sprintf(" (%s:default)", language)
273+
}
274+
if runtime.Deprecated {
275+
tag = " (deprecated)"
276+
}
277+
fmt.Fprintf(c.Out, " %s%s\n", runtime.Kind, tag)
237278
}
238279
}
239280
return nil

commands/sandbox_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,57 @@ func TestSandboxStatusWhenConnected(t *testing.T) {
7373
})
7474
}
7575

76+
func TestSandboxStatusWithLanguages(t *testing.T) {
77+
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
78+
buf := &bytes.Buffer{}
79+
config.Out = buf
80+
config.Doit.Set(config.NS, "languages", true)
81+
fakeCmd := &exec.Cmd{
82+
Stdout: config.Out,
83+
}
84+
fakeHostInfo := do.ServerlessHostInfo{
85+
Runtimes: map[string][]do.ServerlessRuntime{
86+
"go": {
87+
{
88+
Kind: "go:1.20",
89+
Deprecated: true,
90+
Default: false,
91+
},
92+
{
93+
Kind: "go:1.21",
94+
Deprecated: false,
95+
Default: false,
96+
},
97+
{
98+
Kind: "go:1.22",
99+
Deprecated: false,
100+
Default: true,
101+
},
102+
},
103+
},
104+
}
105+
expectedDisplay := `go:
106+
Keywords: go, golang
107+
Runtime versions:
108+
go:1.20 (deprecated)
109+
go:1.21
110+
go:1.22 (go:default)
111+
`
112+
113+
tm.sandbox.EXPECT().Cmd("auth/current", []string{"--apihost", "--name"}).Return(fakeCmd, nil)
114+
tm.sandbox.EXPECT().Exec(fakeCmd).Return(do.SandboxOutput{
115+
Entity: map[string]interface{}{
116+
"name": "hello",
117+
"apihost": "https://api.example.com",
118+
},
119+
}, nil)
120+
tm.sandbox.EXPECT().GetHostInfo("https://api.example.com").Return(fakeHostInfo, nil)
121+
err := RunSandboxStatus(config)
122+
require.NoError(t, err)
123+
assert.Contains(t, buf.String(), expectedDisplay)
124+
})
125+
}
126+
76127
func TestSandboxStatusWhenNotConnected(t *testing.T) {
77128
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
78129
fakeCmd := &exec.Cmd{

do/mocks/SandboxService.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

do/sandbox.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"context"
1818
"encoding/json"
1919
"errors"
20+
"io"
2021
"net/http"
2122
"os"
2223
"os/exec"
@@ -46,13 +47,30 @@ type namespacesResponseBody struct {
4647
Namespace outputNamespace `json:"namespace"`
4748
}
4849

49-
// SandboxService is an interface for interacting with the sandbox plugin
50-
// and with the namespaces service.
50+
// ServerlessRuntime is the type of a runtime entry returned by the API host controller
51+
// of the serverless cluster.
52+
// Only relevant fields unmarshalled
53+
type ServerlessRuntime struct {
54+
Default bool `json:"default"`
55+
Deprecated bool `json:"deprecated"`
56+
Kind string `json:"kind"`
57+
}
58+
59+
// ServerlessHostInfo is the type of the host information return from the API host controller
60+
// of the serverless cluster.
61+
// Only relevant fields unmarshaled.
62+
type ServerlessHostInfo struct {
63+
Runtimes map[string][]ServerlessRuntime `json:"runtimes"`
64+
}
65+
66+
// SandboxService is an interface for interacting with the sandbox plugin,
67+
// with the namespaces service, and with the serverless cluster controller.
5168
type SandboxService interface {
5269
Cmd(string, []string) (*exec.Cmd, error)
5370
Exec(*exec.Cmd) (SandboxOutput, error)
5471
Stream(*exec.Cmd) error
5572
GetSandboxNamespace(context.Context) (SandboxCredentials, error)
73+
GetHostInfo(string) (ServerlessHostInfo, error)
5674
}
5775

5876
type sandboxService struct {
@@ -150,6 +168,20 @@ func (n *sandboxService) GetSandboxNamespace(ctx context.Context) (SandboxCreden
150168
return ans, nil
151169
}
152170

171+
// GetHostInfo returns the HostInfo structure of the provided API host
172+
func (n *sandboxService) GetHostInfo(APIHost string) (ServerlessHostInfo, error) {
173+
endpoint := APIHost + "/api/v1"
174+
resp, err := http.Get(endpoint)
175+
if err != nil {
176+
return ServerlessHostInfo{}, err
177+
}
178+
defer resp.Body.Close()
179+
body, err := io.ReadAll(resp.Body)
180+
var result ServerlessHostInfo
181+
err = json.Unmarshal(body, &result)
182+
return result, err
183+
}
184+
153185
// Assign the correct API host based on the namespace name.
154186
// Every serverless cluster has two domain names, one ending in '.io', the other in '.co'.
155187
// By convention, the portal only returns the '.io' one but 'doctl sbx' must start using

0 commit comments

Comments
 (0)