mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
Stop showing persistent vs ephemeral for resources (#2333)
Signed-off-by: Spike Curtis <spike@coder.com>
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
|
||||||
"github.com/coder/coder/coderd/database"
|
"github.com/coder/coder/coderd/database"
|
||||||
|
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ type WorkspaceResourcesOptions struct {
|
|||||||
// ┌────────────────────────────────────────────────────────────────────────────┐
|
// ┌────────────────────────────────────────────────────────────────────────────┐
|
||||||
// │ RESOURCE STATUS ACCESS │
|
// │ 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 │
|
// │ └─ dev (linux, amd64) ⦾ connecting [10s] coder ssh dev.dev │
|
||||||
// ├────────────────────────────────────────────────────────────────────────────┤
|
// ├────────────────────────────────────────────────────────────────────────────┤
|
||||||
// │ kubernetes_pod.dev ephemeral │
|
// │ kubernetes_pod.dev │
|
||||||
// │ ├─ go (linux, amd64) ⦿ connected coder ssh dev.go │
|
// │ ├─ go (linux, amd64) ⦿ connected coder ssh dev.go │
|
||||||
// │ └─ postgres (linux, amd64) ⦾ disconnected [4s] coder ssh dev.postgres │
|
// │ └─ 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
|
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()
|
tableWriter := table.NewWriter()
|
||||||
if options.Title != "" {
|
if options.Title != "" {
|
||||||
tableWriter.SetTitle(options.Title)
|
tableWriter.SetTitle(options.Title)
|
||||||
}
|
}
|
||||||
tableWriter.SetStyle(table.StyleLight)
|
tableWriter.SetStyle(table.StyleLight)
|
||||||
tableWriter.Style().Options.SeparateColumns = false
|
tableWriter.Style().Options.SeparateColumns = false
|
||||||
row := table.Row{"Resource", "Status"}
|
row := table.Row{"Resource"}
|
||||||
|
if !options.HideAgentState {
|
||||||
|
row = append(row, "Status")
|
||||||
|
}
|
||||||
if !options.HideAccess {
|
if !options.HideAccess {
|
||||||
row = append(row, "Access")
|
row = append(row, "Access")
|
||||||
}
|
}
|
||||||
@ -76,34 +67,29 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resourceAddress := resource.Type + "." + resource.Name
|
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 agents by name for consistent output.
|
||||||
sort.Slice(resource.Agents, func(i, j int) bool {
|
sort.Slice(resource.Agents, func(i, j int) bool {
|
||||||
return resource.Agents[i].Name < resource.Agents[j].Name
|
return resource.Agents[i].Name < resource.Agents[j].Name
|
||||||
})
|
})
|
||||||
_, existsOnStop := addressOnStop[resourceAddress]
|
|
||||||
resourceState := "ephemeral"
|
|
||||||
if existsOnStop {
|
|
||||||
resourceState = "persistent"
|
|
||||||
}
|
|
||||||
// Display a line for the resource.
|
// Display a line for the resource.
|
||||||
tableWriter.AppendRow(table.Row{
|
tableWriter.AppendRow(table.Row{
|
||||||
Styles.Bold.Render(resourceAddress),
|
Styles.Bold.Render(resourceAddress),
|
||||||
Styles.Placeholder.Render(resourceState),
|
"",
|
||||||
"",
|
"",
|
||||||
})
|
})
|
||||||
// Display all agents associated with the resource.
|
// Display all agents associated with the resource.
|
||||||
for index, agent := range resource.Agents {
|
for index, agent := range resource.Agents {
|
||||||
sshCommand := "coder ssh " + options.WorkspaceName
|
pipe := "├"
|
||||||
if totalAgents > 1 {
|
if index == len(resource.Agents)-1 {
|
||||||
sshCommand += "." + agent.Name
|
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
|
var agentStatus string
|
||||||
if !options.HideAgentState {
|
if !options.HideAgentState {
|
||||||
switch agent.Status {
|
switch agent.Status {
|
||||||
@ -119,17 +105,14 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
|
|||||||
agentStatus = Styles.Keyword.Render("⦿ connected")
|
agentStatus = Styles.Keyword.Render("⦿ connected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
row = append(row, agentStatus)
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
if !options.HideAccess {
|
if !options.HideAccess {
|
||||||
|
sshCommand := "coder ssh " + options.WorkspaceName
|
||||||
|
if totalAgents > 1 {
|
||||||
|
sshCommand += "." + agent.Name
|
||||||
|
}
|
||||||
|
sshCommand = Styles.Code.Render(sshCommand)
|
||||||
row = append(row, sshCommand)
|
row = append(row, sshCommand)
|
||||||
}
|
}
|
||||||
tableWriter.AppendRow(row)
|
tableWriter.AppendRow(row)
|
||||||
|
@ -27,7 +27,11 @@ func TestCreate(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
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)
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
args := []string{
|
args := []string{
|
||||||
@ -51,14 +55,19 @@ func TestCreate(t *testing.T) {
|
|||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
matches := []string{
|
matches := []struct {
|
||||||
"Confirm create", "yes",
|
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
|
<-doneChan
|
||||||
})
|
})
|
||||||
|
61
cli/show_test.go
Normal file
61
cli/show_test.go
Normal 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
|
||||||
|
})
|
||||||
|
}
|
@ -230,7 +230,14 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
|
|||||||
return nil, nil, err
|
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,
|
HideAgentState: true,
|
||||||
HideAccess: true,
|
HideAccess: true,
|
||||||
Title: "Template Preview",
|
Title: "Template Preview",
|
||||||
|
@ -14,6 +14,28 @@ import (
|
|||||||
"github.com/coder/coder/pty/ptytest"
|
"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) {
|
func TestTemplateCreate(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("Create", func(t *testing.T) {
|
t.Run("Create", func(t *testing.T) {
|
||||||
@ -22,7 +44,7 @@ func TestTemplateCreate(t *testing.T) {
|
|||||||
coderdtest.CreateFirstUser(t, client)
|
coderdtest.CreateFirstUser(t, client)
|
||||||
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
|
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
Provision: echo.ProvisionComplete,
|
Provision: provisionCompleteWithAgent,
|
||||||
})
|
})
|
||||||
args := []string{
|
args := []string{
|
||||||
"templates",
|
"templates",
|
||||||
@ -49,12 +71,16 @@ func TestTemplateCreate(t *testing.T) {
|
|||||||
write string
|
write string
|
||||||
}{
|
}{
|
||||||
{match: "Create and upload", write: "yes"},
|
{match: "Create and upload", write: "yes"},
|
||||||
|
{match: "compute.main"},
|
||||||
|
{match: "smith (linux, i386)"},
|
||||||
{match: "Confirm create?", write: "yes"},
|
{match: "Confirm create?", write: "yes"},
|
||||||
}
|
}
|
||||||
for _, m := range matches {
|
for _, m := range matches {
|
||||||
pty.ExpectMatch(m.match)
|
pty.ExpectMatch(m.match)
|
||||||
|
if len(m.write) > 0 {
|
||||||
pty.WriteLine(m.write)
|
pty.WriteLine(m.write)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
require.NoError(t, <-execDone)
|
require.NoError(t, <-execDone)
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user