Stop showing persistent vs ephemeral for resources (#2333)

Signed-off-by: Spike Curtis <spike@coder.com>
This commit is contained in:
Spike Curtis
2022-06-16 11:36:11 -07:00
committed by GitHub
parent 552dad6919
commit 93b1425d85
5 changed files with 147 additions and 61 deletions

View File

@ -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,34 +67,29 @@ 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
pipe := "├"
if index == len(resource.Agents)-1 {
pipe = ""
}
sshCommand = Styles.Code.Render(sshCommand)
row := table.Row{
// These tree from a resource!
fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture),
}
if !options.HideAgentState {
var agentStatus string
if !options.HideAgentState {
switch agent.Status {
@ -119,17 +105,14 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
agentStatus = Styles.Keyword.Render("⦿ connected")
}
}
pipe := "├"
if index == len(resource.Agents)-1 {
pipe = "└"
}
row := table.Row{
// These tree from a resource!
fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture),
agentStatus,
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)

View File

@ -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 _, m := range matches {
pty.ExpectMatch(m.match)
if len(m.write) > 0 {
pty.WriteLine(m.write)
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})

61
cli/show_test.go Normal file
View File

@ -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
})
}

View File

@ -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",

View File

@ -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,12 +71,16 @@ 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)
if len(m.write) > 0 {
pty.WriteLine(m.write)
}
}
require.NoError(t, <-execDone)
})