Skip to content
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
11 changes: 11 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,19 @@ type Service struct {
CreatedAt time.Time `json:"createdAt"`
DeployedAt time.Time `json:"deployedAt"`
Domains []string `json:"domains"`
// Kind is the workload type: "function" or "batch". Empty when talking to a
// server that predates the field, which is treated as "function".
Kind string `json:"kind"`
}

// IsBatch reports whether this is a batch service, for which the deployment and
// domain columns are meaningless.
func (s Service) IsBatch() bool { return s.Kind == "batch" }

// Deployed reports whether the service has ever been deployed. A batch service
// never is, and a function service may not have been yet.
func (s Service) Deployed() bool { return !s.DeployedAt.IsZero() }

type Build struct {
ID string `json:"id"`
Constructor string `json:"constructor"`
Expand Down
45 changes: 42 additions & 3 deletions cmd/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@ var serviceListCmd = &cobra.Command{
return services[i].DeployedAt.Before(services[j].DeployedAt)
})
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintln(tw, "NAME\tVERSION\tCREATED\tLAST DEPLOYED\tDOMAINS")
fmt.Fprintln(tw, "NAME\tKIND\tVERSION\tCREATED\tLAST DEPLOYED\tDOMAINS")
for _, svc := range services {
fmt.Fprintln(tw, strings.Join([]string{
svc.Name,
strconv.FormatInt(svc.Deployment, 10),
serviceKind(svc.Kind),
deployedVersion(svc),
humanize.Time(svc.CreatedAt),
humanize.Time(svc.DeployedAt),
lastDeployed(svc),
strings.Join(svc.Domains, " "),
}, "\t"))
}
Expand All @@ -93,6 +94,44 @@ var serviceListCmd = &cobra.Command{
}),
}

// serviceKind renders a service's workload type.
//
// Deliberately uncoloured. tabwriter measures cell width in bytes, so an ANSI
// sequence makes a cell look ~9 characters wider than it prints and the whole
// table skews -- which is already visible wherever colorize() is used inside a
// tabwriter elsewhere in this CLI. A column of its own distinguishes the kinds
// well enough without that cost.
func serviceKind(kind string) string {
if kind == "" {
// A server predating the kind field. Reporting "function" would be a
// guess presented as fact.
return "-"
}
return kind
}

// deployedVersion renders the deployment version, or a dash when there is no
// deployment to have a version.
func deployedVersion(svc api.Service) string {
if !svc.Deployed() {
return "-"
}
return strconv.FormatInt(svc.Deployment, 10)
}

// lastDeployed renders the deployment time, or a dash when the service has
// never been deployed.
//
// Humanising the zero timestamp yields "a long while ago", which reads as
// deployed in the distant past. For a batch service that is not merely
// imprecise: it is never deployed at all, by design.
func lastDeployed(svc api.Service) string {
if !svc.Deployed() {
return "-"
}
return humanize.Time(svc.DeployedAt)
}

var (
serviceLogsFollow bool
serviceLogsTail bool
Expand Down
56 changes: 56 additions & 0 deletions cmd/services_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cmd

import (
"testing"
"time"

"github.com/valar/cli/api"
)

func TestServiceKind(t *testing.T) {
for _, tc := range []struct{ In, Want string }{
{"function", "function"},
{"batch", "batch"},
// A server predating the field must not be reported as "function":
// that would present a guess as fact.
{"", "-"},
{"something-new", "something-new"},
} {
if got := serviceKind(tc.In); got != tc.Want {
t.Errorf("serviceKind(%q) = %q, want %q", tc.In, got, tc.Want)
}
}
}

// Humanising the zero timestamp yields "a long while ago", which reads as
// deployed in the distant past. A batch service is never deployed at all.
func TestDeployedColumns_NeverDeployed(t *testing.T) {
svc := api.Service{Name: "batchdemo", Kind: "batch"}
if got := lastDeployed(svc); got != "-" {
t.Errorf("lastDeployed = %q, want -", got)
}
if got := deployedVersion(svc); got != "-" {
t.Errorf("deployedVersion = %q, want -", got)
}
}

func TestDeployedColumns_Deployed(t *testing.T) {
svc := api.Service{Name: "web", Kind: "function", Deployment: 7, DeployedAt: time.Now().Add(-time.Hour)}
if got := deployedVersion(svc); got != "7" {
t.Errorf("deployedVersion = %q, want 7", got)
}
if got := lastDeployed(svc); got == "-" {
t.Error("lastDeployed should render a time for a deployed service")
}
}

func TestServiceIsBatch(t *testing.T) {
if !(api.Service{Kind: "batch"}).IsBatch() {
t.Error("batch service should report IsBatch")
}
for _, kind := range []string{"function", ""} {
if (api.Service{Kind: kind}).IsBatch() {
t.Errorf("kind %q should not report IsBatch", kind)
}
}
}