Skip to content

Stop showing persistent vs ephemeral for resources #2333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 33 additions & 50 deletions cli/cliui/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/jedib0t/go-pretty/v6/table"

"github.com/coder/coder/coderd/database"

"github.com/coder/coder/codersdk"
)

Expand All @@ -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 │
// └────────────────────────────────────────────────────────────────────────────┘
Expand All @@ -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")
}
Expand All @@ -76,60 +67,52 @@ 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 = "└"
}
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)
Expand Down
25 changes: 17 additions & 8 deletions cli/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
})
Expand Down
61 changes: 61 additions & 0 deletions cli/show_test.go
Original file line number Diff line number Diff line change
@@ -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
})
}
9 changes: 8 additions & 1 deletion cli/templatecreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 28 additions & 2 deletions cli/templatecreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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",
Expand All @@ -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)
Expand Down