Repository: digitalocean/doctl
Pull request #1320 (2022-12-06 12:49:50)

Include label in credentials store and status msg
Up until now, the output of `doctl sls connect` included the label but the output of `doctl sls status` did not.   The reason was that the label was not stored locally and so an additional protocol handshake would be needed to obtain it.  

This change makes the status message more similar to the connect message by including the label in the stored credentials so it is available on the same basis as other relevant information about the namespace, then using common code to format the output message in both cases.

It would arguably be possible to go further with this change, making the label more prominent (and the generated id less prominent) in the message.   I did not do this because a major change in the format of these output messages could break some scripts.  I do not think we commit to exact compatibility on human-readable messages, so this would not be a "breaking change" but I thought it was better to err in the direction of least disruption by keeping the label as an optional field at the end.

Note that the label actually _is_ optional.   For typical customers, where only production  backends are used, it would always be present.   But, for certain development use cases (connecting to non-production backends) there will be no label.


Patch:
diff --git a/commands/serverless.go b/commands/serverless.go
index 09ceecde..6e844dee 100644
--- a/commands/serverless.go
+++ b/commands/serverless.go
@@ -232,7 +232,7 @@ func RunServerlessConnect(c *CmdConfig) error {
 			Namespace:   namespace,
 			Credentials: map[string]map[string]do.ServerlessCredential{apihost: {namespace: credential}},
 		}
-		return finishConnecting(sls, creds, "", c.Out)
+		return finishConnecting(sls, creds, c.Out)
 	}
 	if len(apihost) > 0 || len(auth) > 0 {
 		return fmt.Errorf("If either of 'apihost' or 'auth' is specified then both must be specified")
@@ -290,7 +290,7 @@ func connectFromList(ctx context.Context, sls do.ServerlessService, list []do.Ou
 	if err != nil {
 		return err
 	}
-	return finishConnecting(sls, creds, ns.Label, out)
+	return finishConnecting(sls, creds, out)
 }
 
 // connectChoiceReader is the bufio.Reader for reading the user's response to the prompt to choose
@@ -322,22 +322,27 @@ func chooseFromList(list []do.OutputNamespace, out io.Writer) do.OutputNamespace
 }
 
 // finishConnecting performs the final steps of 'doctl serverless connect'.
-func finishConnecting(sls do.ServerlessService, creds do.ServerlessCredentials, label string, out io.Writer) error {
+func finishConnecting(sls do.ServerlessService, creds do.ServerlessCredentials, out io.Writer) error {
 	// Store the credentials
 	err := sls.WriteCredentials(creds)
 	if err != nil {
 		return err
 	}
 
-	labelTag := ""
-	if label != "" {
-		labelTag = " (label=" + label + ")"
-	}
-	fmt.Fprintf(out, "Connected to functions namespace '%s' on API host '%s'%s\n", creds.Namespace, creds.APIHost, labelTag)
+	fmt.Fprintln(out, getConnectedMessage(creds))
 	fmt.Fprintln(out)
 	return nil
 }
 
+// getConnectedMessage formats the output message for being connected to a specific namespace (with optional label)
+func getConnectedMessage(creds do.ServerlessCredentials) string {
+	labelTag := ""
+	if creds.Label != "" {
+		labelTag = " (label=" + creds.Label + ")"
+	}
+	return fmt.Sprintf("Connected to functions namespace '%s' on API host '%s'%s", creds.Namespace, creds.APIHost, labelTag)
+}
+
 // RunServerlessStatus gives a report on the status of the serverless (installed, up to date, connected)
 func RunServerlessStatus(c *CmdConfig) error {
 	sls := c.Serverless()
@@ -392,7 +397,7 @@ func RunServerlessStatus(c *CmdConfig) error {
 		return nil
 	}
 
-	fmt.Fprintf(c.Out, "Connected to functions namespace '%s' on API host '%s'\n", creds.Namespace, creds.APIHost)
+	fmt.Fprintln(c.Out, getConnectedMessage(creds))
 	fmt.Fprintf(c.Out, "Serverless software version is %s\n\n", do.GetMinServerlessVersion())
 	languages, _ := c.Doit.GetBool(c.NS, "languages")
 	if languages {
diff --git a/do/serverless.go b/do/serverless.go
index e9e16fab..b7749cd2 100644
--- a/do/serverless.go
+++ b/do/serverless.go
@@ -44,6 +44,7 @@ import (
 type ServerlessCredentials struct {
 	APIHost     string                                     `json:"currentHost"`
 	Namespace   string                                     `json:"currentNamespace"`
+	Label       string                                     `json:"label"`
 	Credentials map[string]map[string]ServerlessCredential `json:"credentials"`
 }
 
@@ -627,6 +628,7 @@ func executeNamespaceRequest(ctx context.Context, s *serverlessService, req *htt
 	ans := ServerlessCredentials{
 		APIHost:     host,
 		Namespace:   namespace,
+		Label:       decoded.Namespace.Label,
 		Credentials: map[string]map[string]ServerlessCredential{host: {namespace: credential}},
 	}
 	return ans, nil


Test patch:
diff --git a/commands/serverless_test.go b/commands/serverless_test.go
index dc066b20..2b42cd11 100644
--- a/commands/serverless_test.go
+++ b/commands/serverless_test.go
@@ -98,7 +98,7 @@ func TestServerlessConnect(t *testing.T) {
 				}
 				connectChoiceReader = bufio.NewReader(strings.NewReader("0\n"))
 				nsResponse := do.NamespaceListResponse{Namespaces: tt.namespaceList}
-				creds := do.ServerlessCredentials{Namespace: "ns1", APIHost: "https://api.example.com"}
+				creds := do.ServerlessCredentials{Namespace: "ns1", APIHost: "https://api.example.com", Label: "something"}
 
 				tm.serverless.EXPECT().CheckServerlessStatus().Return(do.ErrServerlessNotConnected)
 				ctx := context.TODO()