Skip to content

Commit

Permalink
Implement --json output for describe/list operations (#505)
Browse files Browse the repository at this point in the history
* implement json support for backends

* implement json support for acls

* implement json support for acl entries

* implement json support for auth tokens

* implement json support for dictionaries

* implement json support for dictionay items

* implement json support for domains

* Fix --verbose output so that --service-name is indicated

* implement json support for health checks

* implement json support for service details

* implement json support for service version

* implement json support for users

* implement json support for vcl custom

* implement json support for vcl snippet

* implement json support for logging
  • Loading branch information
Integralist committed Jan 11, 2022
1 parent e91af24 commit 21d03fb
Show file tree
Hide file tree
Showing 143 changed files with 2,444 additions and 737 deletions.
1 change: 0 additions & 1 deletion .tmpl/create.go
Expand Up @@ -61,7 +61,6 @@ type CreateCommand struct {

// Exec invokes the application logic for the command.
func (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {
// Exit early if no token configured.
_, s := c.Globals.Token()
if s == config.SourceUndefined {
return errors.ErrNoToken
Expand Down
1 change: 0 additions & 1 deletion .tmpl/delete.go
Expand Up @@ -61,7 +61,6 @@ type DeleteCommand struct {

// Exec invokes the application logic for the command.
func (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {
// Exit early if no token configured.
_, s := c.Globals.Token()
if s == config.SourceUndefined {
return errors.ErrNoToken
Expand Down
1 change: 0 additions & 1 deletion .tmpl/describe.go
Expand Up @@ -56,7 +56,6 @@ type DescribeCommand struct {

// Exec invokes the application logic for the command.
func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {
// Exit early if no token configured.
_, s := c.Globals.Token()
if s == config.SourceUndefined {
return errors.ErrNoToken
Expand Down
1 change: 0 additions & 1 deletion .tmpl/list.go
Expand Up @@ -57,7 +57,6 @@ type ListCommand struct {

// Exec invokes the application logic for the command.
func (c *ListCommand) Exec(in io.Reader, out io.Writer) error {
// Exit early if no token configured.
_, s := c.Globals.Token()
if s == config.SourceUndefined {
return errors.ErrNoToken
Expand Down
1 change: 0 additions & 1 deletion .tmpl/update.go
Expand Up @@ -65,7 +65,6 @@ type UpdateCommand struct {

// Exec invokes the application logic for the command.
func (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {
// Exit early if no token configured.
_, s := c.Globals.Token()
if s == config.SourceUndefined {
return errors.ErrNoToken
Expand Down
97 changes: 88 additions & 9 deletions pkg/app/run_test.go

Large diffs are not rendered by default.

55 changes: 38 additions & 17 deletions pkg/cmd/cmd.go
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/fastly/cli/pkg/api"
"github.com/fastly/cli/pkg/config"
"github.com/fastly/cli/pkg/env"
"github.com/fastly/cli/pkg/errors"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/text"
"github.com/fastly/go-fastly/v5/fastly"
Expand Down Expand Up @@ -121,24 +121,17 @@ type ServiceDetailsOpts struct {
ServiceNameFlag OptionalServiceNameID
ServiceVersionFlag OptionalServiceVersion
VerboseMode bool
ErrLog fsterr.LogInterface
}

// ServiceDetails returns the Service ID and Service Version.
func ServiceDetails(opts ServiceDetailsOpts) (serviceID string, serviceVersion *fastly.Version, err error) {
serviceID, source := opts.Manifest.ServiceID()

if source == manifest.SourceUndefined {
if !opts.ServiceNameFlag.WasSet {
return serviceID, serviceVersion, errors.ErrNoServiceID
}
serviceID, err = opts.ServiceNameFlag.Parse(opts.Client)
if err != nil {
return serviceID, serviceVersion, err
}
serviceID, source, flag, err := ServiceID(opts.ServiceNameFlag, opts.Manifest, opts.Client, opts.ErrLog)
if err != nil {
return serviceID, serviceVersion, err
}

if opts.VerboseMode {
DisplayServiceID(serviceID, source, opts.Out)
DisplayServiceID(serviceID, flag, source, opts.Out)
}

v, err := opts.ServiceVersionFlag.Parse(serviceID, opts.Client)
Expand All @@ -153,23 +146,51 @@ func ServiceDetails(opts ServiceDetailsOpts) (serviceID string, serviceVersion *
return serviceID, currentVersion, err
}
} else if !opts.AllowActiveLocked && (v.Active || v.Locked) {
err = errors.RemediationError{
err = fsterr.RemediationError{
Inner: fmt.Errorf("service version %d is not editable", v.Number),
Remediation: errors.AutoCloneRemediation,
Remediation: fsterr.AutoCloneRemediation,
}
return serviceID, v, err
}

return serviceID, v, nil
}

// ServiceID returns the Service ID and the source of that information.
//
// NOTE: If Service ID not provided then check if Service Name provided and use
// that information to acquire the Service ID.
func ServiceID(serviceName OptionalServiceNameID, data manifest.Data, client api.Interface, li fsterr.LogInterface) (serviceID string, source manifest.Source, flag string, err error) {
flag = "--service-id"
serviceID, source = data.ServiceID()

if source == manifest.SourceUndefined {
if !serviceName.WasSet {
err = fsterr.ErrNoServiceID
if li != nil {
li.Add(err)
}
return serviceID, source, flag, err
}

serviceID, err = serviceName.Parse(client)
if err != nil && li != nil {
li.Add(err)
}
flag = "--service-name"
source = manifest.SourceFlag
}

return serviceID, source, flag, err
}

// DisplayServiceID acquires the Service ID (if provided) and displays both it
// and its source location.
func DisplayServiceID(sid string, s manifest.Source, out io.Writer) {
func DisplayServiceID(sid, flag string, s manifest.Source, out io.Writer) {
var via string
switch s {
case manifest.SourceFlag:
via = " (via --service-id)"
via = fmt.Sprintf(" (via %s)", flag)
case manifest.SourceFile:
via = fmt.Sprintf(" (via %s)", manifest.Filename)
case manifest.SourceEnv:
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/common.go
Expand Up @@ -3,6 +3,8 @@ package cmd
var (
FlagCustomerIDName = "customer-id"
FlagCustomerIDDesc = "Alphanumeric string identifying the customer (falls back to FASTLY_CUSTOMER_ID)"
FlagJSONName = "json"
FlagJSONDesc = "Render output as JSON"
FlagServiceIDName = "service-id"
FlagServiceIDDesc = "Service ID (falls back to FASTLY_SERVICE_ID, then fastly.toml)"
FlagServiceName = "service-name"
Expand Down
27 changes: 27 additions & 0 deletions pkg/cmd/flags.go
Expand Up @@ -49,6 +49,33 @@ func (b Base) RegisterFlag(opts StringFlagOpts) {
clause.StringVar(opts.Dst)
}

// BoolFlagOpts enables easy configuration of a flag.
type BoolFlagOpts struct {
Action kingpin.Action
Description string
Dst *bool
Name string
Required bool
Short rune
}

// RegisterFlagBool defines a boolean flag.
//
// TODO: Use generics support in go 1.18 to remove the need for two functions.
func (b Base) RegisterFlagBool(opts BoolFlagOpts) {
clause := b.CmdClause.Flag(opts.Name, opts.Description)
if opts.Short > 0 {
clause = clause.Short(opts.Short)
}
if opts.Required {
clause = clause.Required()
}
if opts.Action != nil {
clause = clause.Action(opts.Action)
}
clause.BoolVar(opts.Dst)
}

// OptionalServiceVersion represents a Fastly service version.
type OptionalServiceVersion struct {
OptionalString
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/acl/create.go
Expand Up @@ -64,6 +64,7 @@ func (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {
serviceID, serviceVersion, err := cmd.ServiceDetails(cmd.ServiceDetailsOpts{
AutoCloneFlag: c.autoClone,
Client: c.Globals.Client,
ErrLog: c.Globals.ErrLog,
Manifest: c.manifest,
Out: out,
ServiceNameFlag: c.serviceName,
Expand Down
37 changes: 32 additions & 5 deletions pkg/commands/acl/describe.go
@@ -1,12 +1,13 @@
package acl

import (
"encoding/json"
"fmt"
"io"

"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/config"
"github.com/fastly/cli/pkg/errors"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/go-fastly/v5/fastly"
)
Expand All @@ -28,6 +29,12 @@ func NewDescribeCommand(parent cmd.Registerer, globals *config.Data, data manife
})

// Optional Flags
c.RegisterFlagBool(cmd.BoolFlagOpts{
Name: cmd.FlagJSONName,
Description: cmd.FlagJSONDesc,
Dst: &c.json,
Short: 'j',
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagServiceIDName,
Description: cmd.FlagServiceIDDesc,
Expand All @@ -48,6 +55,7 @@ func NewDescribeCommand(parent cmd.Registerer, globals *config.Data, data manife
type DescribeCommand struct {
cmd.Base

json bool
manifest manifest.Data
name string
serviceName cmd.OptionalServiceNameID
Expand All @@ -56,6 +64,10 @@ type DescribeCommand struct {

// Exec invokes the application logic for the command.
func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {
if c.Globals.Verbose() && c.json {
return fsterr.ErrInvalidVerboseJSONCombo
}

serviceID, serviceVersion, err := cmd.ServiceDetails(cmd.ServiceDetailsOpts{
AllowActiveLocked: true,
Client: c.Globals.Client,
Expand All @@ -68,7 +80,7 @@ func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"Service ID": serviceID,
"Service Version": errors.ServiceVersion(serviceVersion),
"Service Version": fsterr.ServiceVersion(serviceVersion),
})
return err
}
Expand All @@ -84,7 +96,10 @@ func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {
return err
}

c.print(out, a)
err = c.print(out, a)
if err != nil {
return err
}
return nil
}

Expand All @@ -100,8 +115,19 @@ func (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *
}

// print displays the information returned from the API.
func (c *DescribeCommand) print(out io.Writer, a *fastly.ACL) {
fmt.Fprintf(out, "\nService ID: %s\n", a.ServiceID)
func (c *DescribeCommand) print(out io.Writer, a *fastly.ACL) error {
if c.json {
data, err := json.Marshal(a)
if err != nil {
return err
}
fmt.Fprint(out, string(data))
return nil
}

if !c.Globals.Verbose() {
fmt.Fprintf(out, "\nService ID: %s\n", a.ServiceID)
}
fmt.Fprintf(out, "Service Version: %d\n\n", a.ServiceVersion)
fmt.Fprintf(out, "Name: %s\n", a.Name)
fmt.Fprintf(out, "ID: %s\n\n", a.ID)
Expand All @@ -114,4 +140,5 @@ func (c *DescribeCommand) print(out io.Writer, a *fastly.ACL) {
if a.DeletedAt != nil {
fmt.Fprintf(out, "Deleted at: %s\n", a.DeletedAt)
}
return nil
}
33 changes: 29 additions & 4 deletions pkg/commands/acl/list.go
@@ -1,12 +1,13 @@
package acl

import (
"encoding/json"
"fmt"
"io"

"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/config"
"github.com/fastly/cli/pkg/errors"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/text"
"github.com/fastly/go-fastly/v5/fastly"
Expand All @@ -28,6 +29,12 @@ func NewListCommand(parent cmd.Registerer, globals *config.Data, data manifest.D
})

// Optional Flags
c.RegisterFlagBool(cmd.BoolFlagOpts{
Name: cmd.FlagJSONName,
Description: cmd.FlagJSONDesc,
Dst: &c.json,
Short: 'j',
})
c.RegisterFlag(cmd.StringFlagOpts{
Name: cmd.FlagServiceIDName,
Description: cmd.FlagServiceIDDesc,
Expand All @@ -48,13 +55,18 @@ func NewListCommand(parent cmd.Registerer, globals *config.Data, data manifest.D
type ListCommand struct {
cmd.Base

json bool
manifest manifest.Data
serviceName cmd.OptionalServiceNameID
serviceVersion cmd.OptionalServiceVersion
}

// Exec invokes the application logic for the command.
func (c *ListCommand) Exec(in io.Reader, out io.Writer) error {
if c.Globals.Verbose() && c.json {
return fsterr.ErrInvalidVerboseJSONCombo
}

serviceID, serviceVersion, err := cmd.ServiceDetails(cmd.ServiceDetailsOpts{
AllowActiveLocked: true,
Client: c.Globals.Client,
Expand All @@ -67,7 +79,7 @@ func (c *ListCommand) Exec(in io.Reader, out io.Writer) error {
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"Service ID": serviceID,
"Service Version": errors.ServiceVersion(serviceVersion),
"Service Version": fsterr.ServiceVersion(serviceVersion),
})
return err
}
Expand All @@ -86,7 +98,10 @@ func (c *ListCommand) Exec(in io.Reader, out io.Writer) error {
if c.Globals.Verbose() {
c.printVerbose(out, serviceVersion.Number, as)
} else {
c.printSummary(out, as)
err = c.printSummary(out, as)
if err != nil {
return err
}
}
return nil
}
Expand Down Expand Up @@ -126,11 +141,21 @@ func (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, as []*fast

// printSummary displays the information returned from the API in a summarised
// format.
func (c *ListCommand) printSummary(out io.Writer, as []*fastly.ACL) {
func (c *ListCommand) printSummary(out io.Writer, as []*fastly.ACL) error {
if c.json {
data, err := json.Marshal(as)
if err != nil {
return err
}
fmt.Fprint(out, string(data))
return nil
}

t := text.NewTable(out)
t.AddHeader("SERVICE ID", "VERSION", "NAME", "ID")
for _, a := range as {
t.AddLine(a.ServiceID, a.ServiceVersion, a.Name, a.ID)
}
t.Print()
return nil
}

0 comments on commit 21d03fb

Please sign in to comment.