From 8543cbdfa3397cd04f3a0d196caeda105b9a298d Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 14 Jun 2022 16:11:37 -0700 Subject: [PATCH] Stop showing persistent vs ephemeral for resources Signed-off-by: Spike Curtis --- cli/cliui/resources.go | 83 +++++++++++++++----------------------- cli/create_test.go | 25 ++++++++---- cli/show_test.go | 61 ++++++++++++++++++++++++++++ cli/templatecreate.go | 9 ++++- cli/templatecreate_test.go | 30 +++++++++++++- 5 files changed, 147 insertions(+), 61 deletions(-) create mode 100644 cli/show_test.go diff --git a/cli/cliui/resources.go b/cli/cliui/resources.go index d6d047dc33af8..b9ce239e454af 100644 --- a/cli/cliui/resources.go +++ b/cli/cliui/resources.go @@ -9,6 +9,7 @@ import ( "github.com/jedib0t/go-pretty/v6/table" "github.com/coder/coder/coderd/database" + "github.com/coder/coder/codersdk" ) @@ -23,12 +24,12 @@ type WorkspaceResourcesOptions struct { // ┌────────────────────────────────────────────────────────────────────────────┐ // │ RESOURCE STATUS ACCESS │ // ├────────────────────────────────────────────────────────────────────────────┤ -// │ google_compute_disk.root persistent │ +// │ google_compute_disk.root │ // ├────────────────────────────────────────────────────────────────────────────┤ -// │ google_compute_instance.dev ephemeral │ +// │ google_compute_instance.dev │ // │ └─ dev (linux, amd64) ⦾ connecting [10s] coder ssh dev.dev │ // ├────────────────────────────────────────────────────────────────────────────┤ -// │ kubernetes_pod.dev ephemeral │ +// │ kubernetes_pod.dev │ // │ ├─ go (linux, amd64) ⦿ connected coder ssh dev.go │ // │ └─ postgres (linux, amd64) ⦾ disconnected [4s] coder ssh dev.postgres │ // └────────────────────────────────────────────────────────────────────────────┘ @@ -38,26 +39,16 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource return resources[i].Type < resources[j].Type }) - // Address on stop indexes whether a resource still exists when in the stopped transition. - addressOnStop := map[string]codersdk.WorkspaceResource{} - for _, resource := range resources { - if resource.Transition != codersdk.WorkspaceTransitionStop { - continue - } - addressOnStop[resource.Type+"."+resource.Name] = resource - } - // Displayed stores whether a resource has already been shown. - // Resources can be stored with numerous states, which we - // process prior to display. - displayed := map[string]struct{}{} - tableWriter := table.NewWriter() if options.Title != "" { tableWriter.SetTitle(options.Title) } tableWriter.SetStyle(table.StyleLight) tableWriter.Style().Options.SeparateColumns = false - row := table.Row{"Resource", "Status"} + row := table.Row{"Resource"} + if !options.HideAgentState { + row = append(row, "Status") + } if !options.HideAccess { row = append(row, "Access") } @@ -76,50 +67,20 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource continue } resourceAddress := resource.Type + "." + resource.Name - if _, shown := displayed[resourceAddress]; shown { - // The same resource can have multiple transitions. - continue - } - displayed[resourceAddress] = struct{}{} // Sort agents by name for consistent output. sort.Slice(resource.Agents, func(i, j int) bool { return resource.Agents[i].Name < resource.Agents[j].Name }) - _, existsOnStop := addressOnStop[resourceAddress] - resourceState := "ephemeral" - if existsOnStop { - resourceState = "persistent" - } + // Display a line for the resource. tableWriter.AppendRow(table.Row{ Styles.Bold.Render(resourceAddress), - Styles.Placeholder.Render(resourceState), + "", "", }) // Display all agents associated with the resource. for index, agent := range resource.Agents { - sshCommand := "coder ssh " + options.WorkspaceName - if totalAgents > 1 { - sshCommand += "." + agent.Name - } - sshCommand = Styles.Code.Render(sshCommand) - var agentStatus string - if !options.HideAgentState { - switch agent.Status { - case codersdk.WorkspaceAgentConnecting: - since := database.Now().Sub(agent.CreatedAt) - agentStatus = Styles.Warn.Render("⦾ connecting") + " " + - Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]") - case codersdk.WorkspaceAgentDisconnected: - since := database.Now().Sub(*agent.DisconnectedAt) - agentStatus = Styles.Error.Render("⦾ disconnected") + " " + - Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]") - case codersdk.WorkspaceAgentConnected: - agentStatus = Styles.Keyword.Render("⦿ connected") - } - } - pipe := "├" if index == len(resource.Agents)-1 { pipe = "└" @@ -127,9 +88,31 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource row := table.Row{ // These tree from a resource! fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture), - agentStatus, + } + if !options.HideAgentState { + var agentStatus string + if !options.HideAgentState { + switch agent.Status { + case codersdk.WorkspaceAgentConnecting: + since := database.Now().Sub(agent.CreatedAt) + agentStatus = Styles.Warn.Render("⦾ connecting") + " " + + Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]") + case codersdk.WorkspaceAgentDisconnected: + since := database.Now().Sub(*agent.DisconnectedAt) + agentStatus = Styles.Error.Render("⦾ disconnected") + " " + + Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]") + case codersdk.WorkspaceAgentConnected: + agentStatus = Styles.Keyword.Render("⦿ connected") + } + } + row = append(row, agentStatus) } if !options.HideAccess { + sshCommand := "coder ssh " + options.WorkspaceName + if totalAgents > 1 { + sshCommand += "." + agent.Name + } + sshCommand = Styles.Code.Render(sshCommand) row = append(row, sshCommand) } tableWriter.AppendRow(row) diff --git a/cli/create_test.go b/cli/create_test.go index 1352ee18b0291..63654df7378b6 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -27,7 +27,11 @@ func TestCreate(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: provisionCompleteWithAgent, + ProvisionDryRun: provisionCompleteWithAgent, + }) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) args := []string{ @@ -51,14 +55,19 @@ func TestCreate(t *testing.T) { err := cmd.Execute() assert.NoError(t, err) }() - matches := []string{ - "Confirm create", "yes", + matches := []struct { + match string + write string + }{ + {match: "compute.main"}, + {match: "smith (linux, i386)"}, + {match: "Confirm create", write: "yes"}, } - for i := 0; i < len(matches); i += 2 { - match := matches[i] - value := matches[i+1] - pty.ExpectMatch(match) - pty.WriteLine(value) + for _, m := range matches { + pty.ExpectMatch(m.match) + if len(m.write) > 0 { + pty.WriteLine(m.write) + } } <-doneChan }) diff --git a/cli/show_test.go b/cli/show_test.go new file mode 100644 index 0000000000000..491579a9c6ce6 --- /dev/null +++ b/cli/show_test.go @@ -0,0 +1,61 @@ +package cli_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/pty/ptytest" +) + +func TestShow(t *testing.T) { + t.Parallel() + t.Run("Exists", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: provisionCompleteWithAgent, + ProvisionDryRun: provisionCompleteWithAgent, + }) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + args := []string{ + "show", + workspace.Name, + } + cmd, root := clitest.New(t, args...) + clitest.SetupConfig(t, client, root) + doneChan := make(chan struct{}) + pty := ptytest.New(t) + cmd.SetIn(pty.Input()) + cmd.SetOut(pty.Output()) + go func() { + defer close(doneChan) + err := cmd.Execute() + assert.NoError(t, err) + }() + matches := []struct { + match string + write string + }{ + {match: "compute.main"}, + {match: "smith (linux, i386)"}, + {match: "coder ssh " + workspace.Name}, + } + for _, m := range matches { + pty.ExpectMatch(m.match) + if len(m.write) > 0 { + pty.WriteLine(m.write) + } + } + <-doneChan + }) +} diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 936c574b57396..457bf5cca784b 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -230,7 +230,14 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org return nil, nil, err } - err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{ + // Only display the resources on the start transition, to avoid listing them more than once. + var startResources []codersdk.WorkspaceResource + for _, r := range resources { + if r.Transition == codersdk.WorkspaceTransitionStart { + startResources = append(startResources, r) + } + } + err = cliui.WorkspaceResources(cmd.OutOrStdout(), startResources, cliui.WorkspaceResourcesOptions{ HideAgentState: true, HideAccess: true, Title: "Template Preview", diff --git a/cli/templatecreate_test.go b/cli/templatecreate_test.go index e3d619ccdf629..bbafcbecb32b1 100644 --- a/cli/templatecreate_test.go +++ b/cli/templatecreate_test.go @@ -14,6 +14,28 @@ import ( "github.com/coder/coder/pty/ptytest" ) +var provisionCompleteWithAgent = []*proto.Provision_Response{ + { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{ + { + Type: "compute", + Name: "main", + Agents: []*proto.Agent{ + { + Name: "smith", + OperatingSystem: "linux", + Architecture: "i386", + }, + }, + }, + }, + }, + }, + }, +} + func TestTemplateCreate(t *testing.T) { t.Parallel() t.Run("Create", func(t *testing.T) { @@ -22,7 +44,7 @@ func TestTemplateCreate(t *testing.T) { coderdtest.CreateFirstUser(t, client) source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ Parse: echo.ParseComplete, - Provision: echo.ProvisionComplete, + Provision: provisionCompleteWithAgent, }) args := []string{ "templates", @@ -49,11 +71,15 @@ func TestTemplateCreate(t *testing.T) { write string }{ {match: "Create and upload", write: "yes"}, + {match: "compute.main"}, + {match: "smith (linux, i386)"}, {match: "Confirm create?", write: "yes"}, } for _, m := range matches { pty.ExpectMatch(m.match) - pty.WriteLine(m.write) + if len(m.write) > 0 { + pty.WriteLine(m.write) + } } require.NoError(t, <-execDone)