From c0c657fe51b57dc42b2f76faca75c77cae2f9d2f Mon Sep 17 00:00:00 2001 From: Dj Walker-Morgan Date: Thu, 11 Jun 2020 14:58:35 +0100 Subject: [PATCH] Refactoring for JSON support in output --- api/resource_platform.go | 22 +++++++++++++++++ api/types.go | 8 +++--- cmd/appInfo.go | 12 +++------ cmd/apps.go | 2 +- cmd/certificates.go | 12 ++++----- cmd/command.go | 31 +++++++++++++++--------- cmd/platform.go | 4 +++ cmd/presenters/alloc_checks.go | 4 +++ cmd/presenters/alloc_events.go | 4 +++ cmd/presenters/allocations.go | 4 +++ cmd/presenters/appInfo.go | 4 +++ cmd/presenters/apps.go | 4 +++ cmd/presenters/builds.go | 4 +++ cmd/presenters/certificate.go | 4 +++ cmd/presenters/certificates.go | 4 +++ cmd/presenters/deploymentStatus.go | 4 +++ cmd/presenters/history.go | 4 +++ cmd/presenters/ipAddresses.go | 4 +++ cmd/presenters/presenter.go | 18 ++++++++++++++ cmd/presenters/regionAutoscaleConfigs.go | 4 +++ cmd/presenters/regions.go | 4 +++ cmd/presenters/release.go | 3 +++ cmd/presenters/secrets.go | 3 +++ cmd/presenters/taskServices.go | 4 +++ cmd/presenters/vmSizes.go | 4 +++ cmd/root.go | 3 +++ cmd/status.go | 7 +++--- flyctl/config.go | 10 +++++--- 28 files changed, 157 insertions(+), 38 deletions(-) diff --git a/api/resource_platform.go b/api/resource_platform.go index df2766b27e..793109419a 100644 --- a/api/resource_platform.go +++ b/api/resource_platform.go @@ -1,6 +1,28 @@ package api func (c *Client) PlatformRegions() ([]Region, error) { + query := ` + query { + platform { + regions { + name + code + } + } + } + ` + + req := c.NewRequest(query) + + data, err := c.Run(req) + if err != nil { + return nil, err + } + + return data.Platform.Regions, nil +} + +func (c *Client) PlatformRegionsAll() ([]Region, error) { query := ` query { platform { diff --git a/api/types.go b/api/types.go index 6e78b9244d..8f613bc004 100644 --- a/api/types.go +++ b/api/types.go @@ -394,10 +394,10 @@ type CheckState struct { } type Region struct { - Code string - Name string - Latitude float32 - Longitude float32 + Code string `json:"code"` + Name string `json:"name"` + Latitude float32 `json:"latitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` } type AutoscalingConfig struct { diff --git a/cmd/appInfo.go b/cmd/appInfo.go index bdd11f7a18..29edb18605 100644 --- a/cmd/appInfo.go +++ b/cmd/appInfo.go @@ -6,7 +6,6 @@ import ( "github.com/superfly/flyctl/docstrings" - "github.com/logrusorgru/aurora" "github.com/superfly/flyctl/cmd/presenters" ) @@ -21,26 +20,23 @@ func runAppInfo(ctx *CmdContext) error { return err } - fmt.Println(aurora.Bold("App")) - err = ctx.RenderEx(&presenters.AppInfo{App: *app}, presenters.Options{HideHeader: true, Vertical: true}) + err = ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.AppInfo{App: *app}, HideHeader: true, Vertical: true, Title: "App"}) if err != nil { return err } - fmt.Println(aurora.Bold("Services")) - err = ctx.Render(&presenters.Services{Services: app.Services}) + err = ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.Services{Services: app.Services}, Title: "Services"}) if err != nil { return err } - fmt.Println(aurora.Bold("IP Addresses")) - err = ctx.Render(&presenters.IPAddresses{IPAddresses: app.IPAddresses.Nodes}) + err = ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.IPAddresses{IPAddresses: app.IPAddresses.Nodes}, Title: "IP Adresses"}) if err != nil { return err } if !app.Deployed { - fmt.Println(`App has not been deployed yet. Try running "flyctl deploy --image flyio/hellofly"`) + fmt.Fprintln(ctx.Out, `App has not been deployed yet. Try running "flyctl deploy --image flyio/hellofly"`) } return nil diff --git a/cmd/apps.go b/cmd/apps.go index 74be45d24f..40c98ac171 100644 --- a/cmd/apps.go +++ b/cmd/apps.go @@ -222,7 +222,7 @@ func runAppsCreate(ctx *CmdContext) error { fmt.Println("New app created") - err = ctx.RenderEx(&presenters.AppInfo{App: *app}, presenters.Options{HideHeader: true, Vertical: true}) + err = ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.AppInfo{App: *app}, HideHeader: true, Vertical: true}) if err != nil { return err } diff --git a/cmd/certificates.go b/cmd/certificates.go index 5e28e984ea..d75a979d8c 100644 --- a/cmd/certificates.go +++ b/cmd/certificates.go @@ -31,9 +31,9 @@ func newCertificatesCommand() *Command { create.Command.Args = cobra.ExactArgs(1) certsDeleteStrings := docstrings.Get("certs.delete") - delete := BuildCommand(cmd, runCertDelete, certsDeleteStrings.Usage, certsDeleteStrings.Short, certsDeleteStrings.Long, os.Stdout, requireSession, requireAppName) - delete.Command.Args = cobra.ExactArgs(1) - delete.AddBoolFlag(BoolFlagOpts{Name: "yes", Shorthand: "y", Description: "accept all confirmations"}) + deleteCmd := BuildCommand(cmd, runCertDelete, certsDeleteStrings.Usage, certsDeleteStrings.Short, certsDeleteStrings.Long, os.Stdout, requireSession, requireAppName) + deleteCmd.Command.Args = cobra.ExactArgs(1) + deleteCmd.AddBoolFlag(BoolFlagOpts{Name: "yes", Shorthand: "y", Description: "accept all confirmations"}) certsShowStrings := docstrings.Get("certs.show") show := BuildCommand(cmd, runCertShow, certsShowStrings.Usage, certsShowStrings.Short, certsShowStrings.Long, os.Stdout, requireSession, requireAppName) @@ -63,7 +63,7 @@ func runCertShow(ctx *CmdContext) error { return err } - return ctx.RenderEx(&presenters.Certificate{Certificate: cert}, presenters.Options{Vertical: true}) + return ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.Certificate{Certificate: cert}, Vertical: true}) } func runCertCheck(ctx *CmdContext) error { @@ -74,7 +74,7 @@ func runCertCheck(ctx *CmdContext) error { return err } - return ctx.RenderEx(&presenters.Certificate{Certificate: cert}, presenters.Options{Vertical: true}) + return ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.Certificate{Certificate: cert}, Vertical: true}) } func runCertAdd(ctx *CmdContext) error { @@ -85,7 +85,7 @@ func runCertAdd(ctx *CmdContext) error { return err } - return ctx.RenderEx(&presenters.Certificate{Certificate: cert}, presenters.Options{Vertical: true}) + return ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.Certificate{Certificate: cert}, Vertical: true}) } func runCertDelete(ctx *CmdContext) error { diff --git a/cmd/command.go b/cmd/command.go index a94d8a2a73..30de339ac2 100644 --- a/cmd/command.go +++ b/cmd/command.go @@ -164,20 +164,21 @@ func (ctx *CmdContext) Render(presentable presenters.Presentable) error { return presenter.Render() } -// RenderEx - Render a presentable structure via the context with additional options -func (ctx *CmdContext) RenderEx(presentable presenters.Presentable, options presenters.Options) error { - presenter := &presenters.Presenter{ - Item: presentable, - Out: os.Stdout, - Opts: options, - } - - return presenter.Render() -} +//// RenderEx - Render a presentable structure via the context with additional options +//func (ctx *CmdContext) RenderEx(presentable presenters.Presentable, options presenters.Options) error { +// presenter := &presenters.Presenter{ +// Item: presentable, +// Out: os.Stdout, +// Opts: options, +// } +// +// return presenter.Render() +//} // PresenterOption - options for RenderEx, RenderView, render etc... type PresenterOption struct { Presentable presenters.Presentable + AsJSON bool Vertical bool HideHeader bool Title string @@ -191,10 +192,11 @@ func (ctx *CmdContext) render(out io.Writer, views ...PresenterOption) error { Opts: presenters.Options{ Vertical: v.Vertical, HideHeader: v.HideHeader, + AsJSON: v.AsJSON, }, } - if v.Title != "" { + if v.Title != "" && !v.AsJSON { fmt.Fprintln(out, aurora.Bold(v.Title)) } @@ -208,6 +210,13 @@ func (ctx *CmdContext) render(out io.Writer, views ...PresenterOption) error { // Frender - render a view to a Writer func (ctx *CmdContext) Frender(w io.Writer, views ...PresenterOption) error { + // If JSON output wanted, set in all views + if ctx.GlobalConfig.GetBool(flyctl.ConfigJSONOutput) { + for i, _ := range views { + views[i].AsJSON = true + } + } + return ctx.render(w, views...) } diff --git a/cmd/platform.go b/cmd/platform.go index d6d64ca6e3..a6cf894079 100644 --- a/cmd/platform.go +++ b/cmd/platform.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/superfly/flyctl/flyctl" "os" "github.com/spf13/cobra" @@ -34,6 +36,8 @@ func runPlatformRegions(ctx *CmdContext) error { return err } + fmt.Println(ctx.GlobalConfig.GetBool(flyctl.ConfigJSONOutput)) + return ctx.Frender(ctx.Out, PresenterOption{ Presentable: &presenters.Regions{Regions: regions}, }) diff --git a/cmd/presenters/alloc_checks.go b/cmd/presenters/alloc_checks.go index 6e59ec5e51..9bef534f44 100644 --- a/cmd/presenters/alloc_checks.go +++ b/cmd/presenters/alloc_checks.go @@ -8,6 +8,10 @@ type AllocationChecks struct { Checks []api.CheckState } +func (p *AllocationChecks) APIStruct() interface{} { + return nil +} + func (p *AllocationChecks) FieldNames() []string { return []string{"ID", "Service", "State", "Output"} } diff --git a/cmd/presenters/alloc_events.go b/cmd/presenters/alloc_events.go index a2b517bacb..f360ade5f8 100644 --- a/cmd/presenters/alloc_events.go +++ b/cmd/presenters/alloc_events.go @@ -10,6 +10,10 @@ type AllocationEvents struct { Events []api.AllocationEvent } +func (p *AllocationEvents) APIStruct() interface{} { + return p.Events +} + func (p *AllocationEvents) FieldNames() []string { return []string{"Timestamp", "Type", "Message"} } diff --git a/cmd/presenters/allocations.go b/cmd/presenters/allocations.go index 6f0b7e7969..c744bff071 100644 --- a/cmd/presenters/allocations.go +++ b/cmd/presenters/allocations.go @@ -12,6 +12,10 @@ type Allocations struct { Allocations []*api.AllocationStatus } +func (p *Allocations) APIStruct() interface{} { + return p.Allocations +} + func (p *Allocations) FieldNames() []string { return []string{"ID", "Version", "Region", "Desired", "Status", "Health Checks", "Restarts", "Created"} } diff --git a/cmd/presenters/appInfo.go b/cmd/presenters/appInfo.go index 92662d0dcf..1e5c307da1 100644 --- a/cmd/presenters/appInfo.go +++ b/cmd/presenters/appInfo.go @@ -10,6 +10,10 @@ type AppInfo struct { App api.App } +func (p *AppInfo) APIStruct() interface{} { + return p.App +} + func (p *AppInfo) FieldNames() []string { return []string{"Name", "Owner", "Version", "Status", "Hostname"} } diff --git a/cmd/presenters/apps.go b/cmd/presenters/apps.go index b2fd582825..57ae6f635c 100644 --- a/cmd/presenters/apps.go +++ b/cmd/presenters/apps.go @@ -9,6 +9,10 @@ type Apps struct { Apps []api.App } +func (p *Apps) APIStruct() interface{} { + return nil +} + func (p *Apps) FieldNames() []string { return []string{"Name", "Owner", "Status", "Latest Deploy"} } diff --git a/cmd/presenters/builds.go b/cmd/presenters/builds.go index 377a6828c3..85f73ea4b1 100644 --- a/cmd/presenters/builds.go +++ b/cmd/presenters/builds.go @@ -6,6 +6,10 @@ type Builds struct { Builds []api.Build } +func (p *Builds) APIStruct() interface{} { + return nil +} + func (p *Builds) FieldNames() []string { return []string{"ID", "Status", "User", "Created At", "Updated At"} } diff --git a/cmd/presenters/certificate.go b/cmd/presenters/certificate.go index 7f94613e45..e4dab1fe22 100644 --- a/cmd/presenters/certificate.go +++ b/cmd/presenters/certificate.go @@ -11,6 +11,10 @@ type Certificate struct { Certificate *api.AppCertificate } +func (p *Certificate) APIStruct() interface{} { + return p.Certificate +} + func (p *Certificate) FieldNames() []string { return []string{"Hostname", "Configured", "Issued", "Certificate Authority", "DNS Provider", "DNS Validation Instructions", "DNS Validation Hostname", "DNS Validation Target", "Source", "Created At", "Status"} } diff --git a/cmd/presenters/certificates.go b/cmd/presenters/certificates.go index e0c99899a5..62642abea5 100644 --- a/cmd/presenters/certificates.go +++ b/cmd/presenters/certificates.go @@ -8,6 +8,10 @@ type Certificates struct { Certificates []api.AppCertificate } +func (p *Certificates) APIStruct() interface{} { + return nil +} + func (p *Certificates) FieldNames() []string { return []string{"Hostname", "Created At", "Status"} } diff --git a/cmd/presenters/deploymentStatus.go b/cmd/presenters/deploymentStatus.go index 10ed706624..14c0e4cb40 100644 --- a/cmd/presenters/deploymentStatus.go +++ b/cmd/presenters/deploymentStatus.go @@ -10,6 +10,10 @@ type DeploymentStatus struct { Status *api.DeploymentStatus } +func (p *DeploymentStatus) APIStruct() interface{} { + return p.Status +} + func (p *DeploymentStatus) FieldNames() []string { return []string{"ID", "Version", "Status", "Description", "Allocations"} } diff --git a/cmd/presenters/history.go b/cmd/presenters/history.go index 6e12704b08..f7e793933b 100644 --- a/cmd/presenters/history.go +++ b/cmd/presenters/history.go @@ -8,6 +8,10 @@ type AppHistory struct { AppChanges []api.AppChange } +func (p *AppHistory) APIStruct() interface{} { + return nil +} + func (p *AppHistory) FieldNames() []string { return []string{"Type", "Status", "Description", "User", "Date"} } diff --git a/cmd/presenters/ipAddresses.go b/cmd/presenters/ipAddresses.go index eb844769c5..1c4c40feac 100644 --- a/cmd/presenters/ipAddresses.go +++ b/cmd/presenters/ipAddresses.go @@ -8,6 +8,10 @@ type IPAddresses struct { IPAddresses []api.IPAddress } +func (p *IPAddresses) APIStruct() interface{} { + return nil +} + func (p *IPAddresses) FieldNames() []string { return []string{"Type", "Address", "Created At"} } diff --git a/cmd/presenters/presenter.go b/cmd/presenters/presenter.go index 26d52f8b82..0e5c60bb0f 100644 --- a/cmd/presenters/presenter.go +++ b/cmd/presenters/presenter.go @@ -1,6 +1,7 @@ package presenters import ( + "encoding/json" "fmt" "io" @@ -11,6 +12,7 @@ import ( type Presentable interface { FieldNames() []string Records() []map[string]string + APIStruct() interface{} } // Presenter - A self managing presenter which can be rendered in multiple ways @@ -24,10 +26,15 @@ type Presenter struct { type Options struct { Vertical bool HideHeader bool + AsJSON bool } // Render - Renders a presenter as a field list or table func (p *Presenter) Render() error { + if p.Opts.AsJSON { + return p.renderJSON() + } + if p.Opts.Vertical { return p.renderFieldList() } @@ -89,3 +96,14 @@ func (p *Presenter) renderFieldList() error { return nil } + +func (p *Presenter) renderJSON() error { + data := p.Item.APIStruct() + if data == nil { + return fmt.Errorf("JSON output not available") + } + prettyJSON, err := json.MarshalIndent(p.Item.APIStruct(), "", " ") + fmt.Fprintln(p.Out, string(prettyJSON)) + + return err +} diff --git a/cmd/presenters/regionAutoscaleConfigs.go b/cmd/presenters/regionAutoscaleConfigs.go index 80ce7b2021..480e03958e 100644 --- a/cmd/presenters/regionAutoscaleConfigs.go +++ b/cmd/presenters/regionAutoscaleConfigs.go @@ -10,6 +10,10 @@ type AutoscalingRegionConfigs struct { Regions []api.AutoscalingRegionConfig } +func (p *AutoscalingRegionConfigs) APIStruct() interface{} { + return nil +} + func (p *AutoscalingRegionConfigs) FieldNames() []string { return []string{"Region", "Min Count", "Weight"} } diff --git a/cmd/presenters/regions.go b/cmd/presenters/regions.go index 5c49e1062c..09ce33319f 100644 --- a/cmd/presenters/regions.go +++ b/cmd/presenters/regions.go @@ -8,6 +8,10 @@ type Regions struct { Regions []api.Region } +func (p *Regions) APIStruct() interface{} { + return p.Regions +} + func (p *Regions) FieldNames() []string { return []string{"Code", "Name"} } diff --git a/cmd/presenters/release.go b/cmd/presenters/release.go index 16e8491ea0..dc32203504 100644 --- a/cmd/presenters/release.go +++ b/cmd/presenters/release.go @@ -12,6 +12,9 @@ type Releases struct { Release *api.Release } +func (p *Releases) APIStruct() interface{} { + return nil +} func (p *Releases) FieldNames() []string { return []string{"Version", "Stable", "Type", "Status", "Description", "User", "Date"} } diff --git a/cmd/presenters/secrets.go b/cmd/presenters/secrets.go index 34e64c2a8c..4ebe487065 100644 --- a/cmd/presenters/secrets.go +++ b/cmd/presenters/secrets.go @@ -8,6 +8,9 @@ type Secrets struct { Secrets []api.Secret } +func (p *Secrets) APIStruct() interface{} { + return nil +} func (p *Secrets) FieldNames() []string { return []string{"Name", "Digest", "Date"} } diff --git a/cmd/presenters/taskServices.go b/cmd/presenters/taskServices.go index 170587300a..f627339673 100644 --- a/cmd/presenters/taskServices.go +++ b/cmd/presenters/taskServices.go @@ -11,6 +11,10 @@ type Services struct { Services []api.Service } +func (p *Services) APIStruct() interface{} { + return p.Services +} + func (p *Services) FieldNames() []string { return []string{"Protocol", "Ports"} } diff --git a/cmd/presenters/vmSizes.go b/cmd/presenters/vmSizes.go index ffa61cbe55..7d7c1f0e59 100644 --- a/cmd/presenters/vmSizes.go +++ b/cmd/presenters/vmSizes.go @@ -10,6 +10,10 @@ type VMSizes struct { VMSizes []api.VMSize } +func (p *VMSizes) APIStruct() interface{} { + return nil +} + func (p *VMSizes) FieldNames() []string { return []string{"Name", "CPU Cores", "Memory", "Price (Second)", "Price (Month)"} } diff --git a/cmd/root.go b/cmd/root.go index 3626babbd7..f9172ef357 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -52,6 +52,9 @@ func init() { rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output") viper.BindPFlag(flyctl.ConfigVerboseOutput, rootCmd.PersistentFlags().Lookup("verbose")) + rootCmd.PersistentFlags().BoolP("json", "j", false, "json output") + viper.BindPFlag(flyctl.ConfigJSONOutput, rootCmd.PersistentFlags().Lookup("json")) + rootCmd.AddCommand( newAuthCommand(), newAppStatusCommand(), diff --git a/cmd/status.go b/cmd/status.go index 16d6273e54..dd7c5a07bb 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -32,8 +32,7 @@ func runAppStatus(ctx *CmdContext) error { return err } - fmt.Println(aurora.Bold("App")) - err = ctx.RenderEx(&presenters.AppInfo{App: *app}, presenters.Options{HideHeader: true, Vertical: true}) + err = ctx.Frender(ctx.Out, PresenterOption{Presentable: &presenters.AppInfo{App: *app}, HideHeader: true, Vertical: true, Title: "App"}) if err != nil { return err } @@ -44,10 +43,10 @@ func runAppStatus(ctx *CmdContext) error { } if app.DeploymentStatus != nil { - fmt.Println(aurora.Bold("Deployment Status")) err = ctx.Frender(ctx.Out, PresenterOption{ Presentable: &presenters.DeploymentStatus{Status: app.DeploymentStatus}, Vertical: true, + Title: "Deployment Status", }) if err != nil { @@ -55,9 +54,9 @@ func runAppStatus(ctx *CmdContext) error { } } - fmt.Println(aurora.Bold("Allocations")) err = ctx.Frender(ctx.Out, PresenterOption{ Presentable: &presenters.Allocations{Allocations: app.Allocations}, + Title: "Allocations", }) if err != nil { return err diff --git a/flyctl/config.go b/flyctl/config.go index 68c7810bb4..13c8cb5237 100644 --- a/flyctl/config.go +++ b/flyctl/config.go @@ -5,10 +5,12 @@ import ( ) const ( - ConfigAPIToken = "access_token" - ConfigAPIBaseURL = "api_base_url" - ConfigAppName = "app" - ConfigVerboseOutput = "verbose" + ConfigAPIToken = "access_token" + ConfigAPIBaseURL = "api_base_url" + ConfigAppName = "app" + ConfigVerboseOutput = "verbose" + ConfigJSONOutput = "json" + ConfigRegistryHost = "registry_host" ConfigUpdateCheckLatestVersion = "update_check.latest_version" ConfigUpdateCheckTimestamp = "update_check.timestamp"