Skip to content

Commit e76bea1

Browse files
Print deployment results as a table (#1033)
* refactor printServiceStatesAndEndpoints to print Table * only show domains if they are present * use Status instead of State * use ID instead of Etag * use Deployment instead of Id * use state instead of status * use Deployment instead of Id * Revert "use state instead of status" This reverts commit 02d350a. * rename Etag to Deployment for internal structs (#1089)
1 parent 9ae15da commit e76bea1

14 files changed

+185
-90
lines changed

src/cmd/cli/command/cd.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ var cdPreviewCmd = &cobra.Command{
200200
}
201201

202202
tailOptions := cli.TailOptions{
203-
Etag: resp.Etag,
204-
Verbose: verbose,
205-
LogType: logs.LogTypeAll,
203+
Deployment: resp.Etag,
204+
Verbose: verbose,
205+
LogType: logs.LogTypeAll,
206206
}
207207
return cli.Tail(cmd.Context(), provider, project.Name, tailOptions)
208208
},

src/cmd/cli/command/commands.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ func SetupCommands(ctx context.Context, version string) {
254254

255255
// Debug Command
256256
debugCmd.Flags().String("etag", "", "deployment ID (ETag) of the service")
257+
debugCmd.Flags().MarkHidden("etag")
258+
debugCmd.Flags().String("deployment", "", "deployment ID of the service")
257259
debugCmd.Flags().String("since", "", "start time for logs (RFC3339 format)")
258260
debugCmd.Flags().String("until", "", "end time for logs (RFC3339 format)")
259261
debugCmd.Flags().StringVar(&modelId, "model", modelId, "LLM model to use for debugging (Pro users only)")
@@ -865,9 +867,14 @@ var debugCmd = &cobra.Command{
865867
Short: "Debug a build, deployment, or service failure",
866868
RunE: func(cmd *cobra.Command, args []string) error {
867869
etag, _ := cmd.Flags().GetString("etag")
870+
deployment, _ := cmd.Flags().GetString("deployment")
868871
since, _ := cmd.Flags().GetString("since")
869872
until, _ := cmd.Flags().GetString("until")
870873

874+
if etag != "" && deployment == "" {
875+
deployment = etag
876+
}
877+
871878
loader := configureLoader(cmd)
872879
provider, err := getProvider(cmd.Context(), loader)
873880
if err != nil {
@@ -890,7 +897,7 @@ var debugCmd = &cobra.Command{
890897
}
891898

892899
debugConfig := cli.DebugConfig{
893-
Etag: etag,
900+
Deployment: deployment,
894901
FailedServices: args,
895902
ModelId: modelId,
896903
Project: project,
@@ -930,7 +937,7 @@ var deleteCmd = &cobra.Command{
930937
}
931938

932939
since := time.Now()
933-
etag, err := cli.Delete(cmd.Context(), projectName, client, provider, names...)
940+
deployment, err := cli.Delete(cmd.Context(), projectName, client, provider, names...)
934941
if err != nil {
935942
if connect.CodeOf(err) == connect.CodeNotFound {
936943
// Show a warning (not an error) if the service was not found
@@ -940,20 +947,20 @@ var deleteCmd = &cobra.Command{
940947
return err
941948
}
942949

943-
term.Info("Deleted service", names, "with deployment ID", etag)
950+
term.Info("Deleted service", names, "with deployment ID", deployment)
944951

945952
if !tail {
946-
printDefangHint("To track the update, do:", "tail --etag "+etag)
953+
printDefangHint("To track the update, do:", "tail --deployment "+deployment)
947954
return nil
948955
}
949956

950957
term.Info("Tailing logs for update; press Ctrl+C to detach:")
951958

952959
tailOptions := cli.TailOptions{
953-
Etag: etag,
954-
LogType: logs.LogTypeAll,
955-
Since: since,
956-
Verbose: verbose,
960+
Deployment: deployment,
961+
LogType: logs.LogTypeAll,
962+
Since: since,
963+
Verbose: verbose,
957964
}
958965
return cli.Tail(cmd.Context(), provider, projectName, tailOptions)
959966
},

src/cmd/cli/command/compose.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ func makeComposeUpCmd() *cobra.Command {
170170
// Tail got canceled because of deployment failure: prompt to show the debugger
171171
term.Warn(errDeploymentFailed)
172172
debugConfig := cli.DebugConfig{
173-
Etag: deploy.Etag,
174-
ModelId: modelId,
175-
Project: project,
176-
Provider: provider,
177-
Since: since,
173+
Deployment: deploy.Etag,
174+
ModelId: modelId,
175+
Project: project,
176+
Provider: provider,
177+
Since: since,
178178
}
179179
if errDeploymentFailed.Service != "" {
180180
debugConfig.FailedServices = []string{errDeploymentFailed.Service}
@@ -196,7 +196,10 @@ func makeComposeUpCmd() *cobra.Command {
196196
}
197197

198198
// Print the current service states of the deployment
199-
printServiceStatesAndEndpoints(deploy.Services)
199+
err = printServiceStatesAndEndpoints(deploy.Services)
200+
if err != nil {
201+
return err
202+
}
200203

201204
term.Info("Done.")
202205
return nil
@@ -289,7 +292,7 @@ func makeComposeDownCmd() *cobra.Command {
289292
}
290293

291294
since := time.Now()
292-
etag, err := cli.ComposeDown(cmd.Context(), projectName, client, provider, args...)
295+
deployment, err := cli.ComposeDown(cmd.Context(), projectName, client, provider, args...)
293296
if err != nil {
294297
if connect.CodeOf(err) == connect.CodeNotFound {
295298
// Show a warning (not an error) if the service was not found
@@ -299,10 +302,10 @@ func makeComposeDownCmd() *cobra.Command {
299302
return err
300303
}
301304

302-
term.Info("Deleted services, deployment ID", etag)
305+
term.Info("Deleted services, deployment ID", deployment)
303306

304307
if detach {
305-
printDefangHint("To track the update, do:", "tail --etag "+etag)
308+
printDefangHint("To track the update, do:", "tail --deployment "+deployment)
306309
return nil
307310
}
308311

@@ -311,7 +314,7 @@ func makeComposeDownCmd() *cobra.Command {
311314
{Service: "cd", Host: "pulumi", EventLog: "Update succeeded in "},
312315
}
313316
tailOptions := cli.TailOptions{
314-
Etag: etag,
317+
Deployment: deployment,
315318
Since: since,
316319
EndEventDetectFunc: cli.CreateEndLogEventDetectFunc(endLogConditions),
317320
Verbose: verbose,
@@ -431,13 +434,18 @@ func makeComposeLogsCmd() *cobra.Command {
431434
RunE: func(cmd *cobra.Command, args []string) error {
432435
var name, _ = cmd.Flags().GetString("name")
433436
var etag, _ = cmd.Flags().GetString("etag")
437+
var deployment, _ = cmd.Flags().GetString("deployment")
434438
var raw, _ = cmd.Flags().GetBool("raw")
435439
var since, _ = cmd.Flags().GetString("since")
436440
var utc, _ = cmd.Flags().GetBool("utc")
437441
var verbose, _ = cmd.Flags().GetBool("verbose")
438442
var filter, _ = cmd.Flags().GetString("filter")
439443
var until, _ = cmd.Flags().GetString("until")
440444

445+
if etag != "" && deployment == "" {
446+
deployment = etag
447+
}
448+
441449
if utc {
442450
cli.EnableUTCMode()
443451
}
@@ -484,14 +492,14 @@ func makeComposeLogsCmd() *cobra.Command {
484492
}
485493

486494
tailOptions := cli.TailOptions{
487-
Etag: etag,
488-
Filter: filter,
489-
LogType: logType,
490-
Raw: raw,
491-
Services: services,
492-
Since: sinceTs,
493-
Until: untilTs,
494-
Verbose: verbose,
495+
Deployment: deployment,
496+
Filter: filter,
497+
LogType: logType,
498+
Raw: raw,
499+
Services: services,
500+
Since: sinceTs,
501+
Until: untilTs,
502+
Verbose: verbose,
495503
}
496504

497505
return cli.Tail(cmd.Context(), provider, projectName, tailOptions)
@@ -500,6 +508,8 @@ func makeComposeLogsCmd() *cobra.Command {
500508
logsCmd.Flags().StringP("name", "n", "", "name of the service (backwards compat)")
501509
logsCmd.Flags().MarkHidden("name")
502510
logsCmd.Flags().String("etag", "", "deployment ID (ETag) of the service")
511+
logsCmd.Flags().MarkHidden("etag")
512+
logsCmd.Flags().String("deployment", "", "deployment ID of the service")
503513
logsCmd.Flags().Bool("follow", false, "follow log output") // NOTE: -f is already used by --file
504514
logsCmd.Flags().MarkHidden("follow") // TODO: implement this
505515
logsCmd.Flags().BoolP("raw", "r", false, "show raw (unparsed) logs")
Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package command
22

33
import (
4+
"strings"
5+
46
"github.com/DefangLabs/defang/src/pkg/cli"
57
cliClient "github.com/DefangLabs/defang/src/pkg/cli/client"
68
"github.com/DefangLabs/defang/src/pkg/term"
@@ -20,31 +22,50 @@ func printPlaygroundPortalServiceURLs(serviceInfos []*defangv1.ServiceInfo) {
2022
}
2123
}
2224

23-
func printServiceStatesAndEndpoints(serviceInfos []*defangv1.ServiceInfo) {
24-
for _, serviceInfo := range serviceInfos {
25-
andEndpoints := ""
26-
if len(serviceInfo.Endpoints) > 0 {
27-
andEndpoints = "and will be available at:"
28-
}
25+
type ServiceTableItem struct {
26+
Deployment string `json:"Deployment"`
27+
Status string `json:"Status"`
28+
Name string `json:"Name"`
29+
DomainName string `json:"DomainName"`
30+
Endpoints string `json:"Endpoints"`
31+
}
2932

30-
serviceConditionText := "has status " + serviceInfo.Status
31-
if serviceInfo.State != defangv1.ServiceState_NOT_SPECIFIED {
32-
serviceConditionText = "is in state " + serviceInfo.State.String()
33-
}
33+
func printServiceStatesAndEndpoints(serviceInfos []*defangv1.ServiceInfo) error {
34+
serviceTableItems := make([]ServiceTableItem, 0, len(serviceInfos))
3435

35-
term.Info("Service", serviceInfo.Service.Name, serviceConditionText, andEndpoints)
36-
for i, endpoint := range serviceInfo.Endpoints {
37-
if serviceInfo.Service.Ports[i].Mode == defangv1.Mode_INGRESS {
38-
endpoint = "https://" + endpoint
39-
}
40-
term.Println(" -", endpoint)
41-
}
36+
showDomainNameColumn := false
37+
showCertGenerateHint := false
38+
for _, serviceInfo := range serviceInfos {
39+
var domainname string
4240
if serviceInfo.Domainname != "" {
43-
if serviceInfo.ZoneId != "" {
44-
term.Println(" -", "https://"+serviceInfo.Domainname)
45-
} else {
46-
term.Println(" -", "https://"+serviceInfo.Domainname+" (after `defang cert generate` to get a TLS certificate)")
41+
showDomainNameColumn = true
42+
domainname = "https://" + serviceInfo.Domainname
43+
if serviceInfo.ZoneId == "" {
44+
showCertGenerateHint = true
4745
}
4846
}
47+
serviceTableItems = append(serviceTableItems, ServiceTableItem{
48+
Deployment: serviceInfo.Etag,
49+
Name: serviceInfo.Service.Name,
50+
Status: serviceInfo.State.String(),
51+
DomainName: domainname,
52+
Endpoints: strings.Join(serviceInfo.Endpoints, ", "),
53+
})
4954
}
55+
56+
attrs := []string{"Deployment", "Name", "Status", "Endpoints"}
57+
if showDomainNameColumn {
58+
attrs = append(attrs, "DomainName")
59+
}
60+
61+
err := term.Table(serviceTableItems, attrs)
62+
if err != nil {
63+
return err
64+
}
65+
66+
if showCertGenerateHint {
67+
term.Info("Run `defang cert generate` to get a TLS certificate for your service(s)")
68+
}
69+
70+
return nil
5071
}

src/cmd/cli/command/deploymentinfo_test.go

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package command
33
import (
44
"bytes"
55
"os"
6+
"strings"
67
"testing"
78

89
"github.com/DefangLabs/defang/src/pkg/cli"
@@ -43,7 +44,7 @@ func TestPrintServiceStatesAndEndpoints(t *testing.T) {
4344
var stdout, stderr bytes.Buffer
4445
term.DefaultTerm = term.NewTerm(os.Stdin, &stdout, &stderr)
4546

46-
printServiceStatesAndEndpoints([]*defangv1.ServiceInfo{
47+
_ = printServiceStatesAndEndpoints([]*defangv1.ServiceInfo{
4748
{
4849
Service: &defangv1.Service{
4950
Name: "service1",
@@ -58,11 +59,65 @@ func TestPrintServiceStatesAndEndpoints(t *testing.T) {
5859
"service1.internal",
5960
},
6061
}})
61-
const want = ` * Service service1 has status UNKNOWN and will be available at:
62-
- https://example.com
63-
- service1.internal
62+
const expectedOutput = `Deployment Name Status Endpoints
63+
service1 NOT_SPECIFIED example.com, service1.internal
6464
`
65-
if got := stdout.String(); got != want {
66-
t.Errorf("got %q, want %q", got, want)
65+
receivedLines := strings.Split(stdout.String(), "\n")
66+
expectedLines := strings.Split(expectedOutput, "\n")
67+
68+
if len(receivedLines) != len(expectedLines) {
69+
t.Errorf("Expected %v lines, received %v", len(expectedLines), len(receivedLines))
70+
}
71+
72+
for i, receivedLine := range receivedLines {
73+
receivedLine = strings.TrimRight(receivedLine, " ")
74+
if receivedLine != expectedLines[i] {
75+
t.Errorf("\n-%v\n+%v", expectedLines[i], receivedLine)
76+
}
77+
}
78+
}
79+
80+
func TestPrintServiceStatesAndEndpointsAndDomainname(t *testing.T) {
81+
defaultTerm := term.DefaultTerm
82+
t.Cleanup(func() {
83+
term.DefaultTerm = defaultTerm
84+
})
85+
86+
var stdout, stderr bytes.Buffer
87+
term.DefaultTerm = term.NewTerm(os.Stdin, &stdout, &stderr)
88+
89+
_ = printServiceStatesAndEndpoints([]*defangv1.ServiceInfo{
90+
{
91+
Service: &defangv1.Service{
92+
Name: "service1",
93+
Ports: []*defangv1.Port{
94+
{Mode: defangv1.Mode_INGRESS},
95+
{Mode: defangv1.Mode_HOST},
96+
},
97+
},
98+
Status: "UNKNOWN",
99+
Domainname: "example.com",
100+
Endpoints: []string{
101+
"example.com",
102+
"service1.internal",
103+
},
104+
}})
105+
expectedLines := []string{
106+
"Deployment Name Status Endpoints DomainName",
107+
" service1 NOT_SPECIFIED example.com, service1.internal https://example.com",
108+
" * Run `defang cert generate` to get a TLS certificate for your service(s)",
109+
"",
110+
}
111+
receivedLines := strings.Split(stdout.String(), "\n")
112+
113+
if len(receivedLines) != len(expectedLines) {
114+
t.Errorf("Expected %v lines, received %v", len(expectedLines), len(receivedLines))
115+
}
116+
117+
for i, receivedLine := range receivedLines {
118+
receivedLine = strings.TrimRight(receivedLine, " ")
119+
if receivedLine != expectedLines[i] {
120+
t.Errorf("\n-%v\n+%v", expectedLines[i], receivedLine)
121+
}
67122
}
68123
}

src/pkg/cli/bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func BootstrapCommand(ctx context.Context, projectName string, verbose bool, p c
2323
return err
2424
}
2525

26-
return tail(ctx, p, projectName, TailOptions{Etag: etag, Since: since, LogType: logs.LogTypeBuild, Verbose: verbose})
26+
return tail(ctx, p, projectName, TailOptions{Deployment: etag, Since: since, LogType: logs.LogTypeBuild, Verbose: verbose})
2727
}
2828

2929
func SplitProjectStack(name string) (projectName string, stackName string) {

src/pkg/cli/composeUp.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ func ComposeUp(ctx context.Context, project *compose.Project, c client.FabricCli
145145
}
146146

147147
func TailUp(ctx context.Context, provider client.Provider, project *compose.Project, deploy *defangv1.DeployResponse, tailOptions TailOptions) error {
148-
if tailOptions.Etag == "" {
149-
tailOptions.Etag = deploy.Etag
148+
if tailOptions.Deployment == "" {
149+
tailOptions.Deployment = deploy.Etag
150150
}
151151
if tailOptions.Since.IsZero() {
152152
tailOptions.Since = time.Now()
@@ -232,11 +232,11 @@ func WaitAndTail(ctx context.Context, project *compose.Project, client client.Fa
232232

233233
func NewTailOptionsForDeploy(deploy *defangv1.DeployResponse, since time.Time, verbose bool) TailOptions {
234234
return TailOptions{
235-
Etag: deploy.Etag,
236-
Since: since,
237-
Raw: false,
238-
Verbose: verbose,
239-
LogType: logs.LogTypeAll,
235+
Deployment: deploy.Etag,
236+
Since: since,
237+
Raw: false,
238+
Verbose: verbose,
239+
LogType: logs.LogTypeAll,
240240
}
241241
}
242242

0 commit comments

Comments
 (0)