diff --git a/cmd/flags.go b/cmd/flags.go index 82bab138a..e5e850b4e 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -54,6 +54,7 @@ const ( flagDstClientID = "dst-client-id" flagSrcConnID = "src-connection-id" flagDstConnID = "dst-connection-id" + flagOutput = "output" ) const ( @@ -381,3 +382,11 @@ func OverwriteConfigFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command { } return cmd } + +func addOutputFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command { + cmd.Flags().StringP(flagOutput, "o", "legacy", "Specify the console output format. Can be 'legacy' or 'json'.") + if err := v.BindPFlag(flagOutput, cmd.Flags().Lookup(flagOutput)); err != nil { + panic(err) + } + return cmd +} diff --git a/cmd/query.go b/cmd/query.go index 15ccd743a..2f3c2e3e9 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -15,6 +15,11 @@ import ( "github.com/spf13/cobra" ) +const ( + formatJson = "json" + formatLegacy = "legacy" +) + // queryCmd represents the chain command func queryCmd(a *appState) *cobra.Command { cmd := &cobra.Command{ @@ -63,7 +68,7 @@ func feegrantQueryCmd(a *appState) *cobra.Command { cmd.AddCommand( feegrantBasicGrantsCmd(a), ) - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -99,7 +104,7 @@ $ %s q ibc-denoms ibc-0`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -127,7 +132,7 @@ $ %s q denom-trace osmosis 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118D9 return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -161,7 +166,7 @@ $ %s q tx ibc-0 A5DF8D272F1C451CFF92BA6C41942C4D29B5CF180279439ED6AB038282F956BE return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -213,7 +218,9 @@ $ %s q txs ibc-0 "message.action=transfer"`, }, } - return paginationFlags(a.viper, cmd, "txs") + cmd = addOutputFlag(a.viper, cmd) + cmd = paginationFlags(a.viper, cmd, "txs") + return cmd } func queryBalanceCmd(a *appState) *cobra.Command { @@ -257,12 +264,34 @@ $ %s query balance ibc-0 testkey`, return err } - fmt.Fprintf(cmd.OutOrStdout(), "address {%s} balance {%s} \n", addr, coins) + // Create a map to hold the data + data := map[string]string{ + "address": addr, + "balance": coins.String(), + } + + // Convert the map to a JSON string + jsonOutput, err := json.Marshal(data) + if err != nil { + return err + } + + output, _ := cmd.Flags().GetString(flagOutput) + switch output { + case formatJson: + fmt.Fprint(cmd.OutOrStdout(), string(jsonOutput)) + case formatLegacy: + fallthrough + default: + fmt.Fprintf(cmd.OutOrStdout(), "address {%s} balance {%s} \n", addr, coins) + } return nil }, } - return ibcDenomFlags(a.viper, cmd) + cmd = addOutputFlag(a.viper, cmd) + cmd = ibcDenomFlags(a.viper, cmd) + return cmd } func queryHeaderCmd(a *appState) *cobra.Command { @@ -309,11 +338,21 @@ $ %s query header ibc-0 1400`, return err } - fmt.Fprintln(cmd.OutOrStdout(), s) + output, _ := cmd.Flags().GetString(flagOutput) + switch output { + case formatJson: + fmt.Fprintln(cmd.OutOrStdout(), string(s)) + case formatLegacy: + fallthrough + default: + fmt.Fprintln(cmd.OutOrStdout(), s) + } + return nil }, } + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -355,7 +394,7 @@ $ %s q node-state ibc-1`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -406,8 +445,9 @@ $ %s query client ibc-0 ibczeroclient --height 1205`, return nil }, } - - return heightFlag(a.viper, cmd) + cmd = addOutputFlag(a.viper, cmd) + cmd = heightFlag(a.viper, cmd) + return cmd } func queryClientsCmd(a *appState) *cobra.Command { @@ -451,8 +491,9 @@ $ %s query clients ibc-2 --offset 2 --limit 30`, return nil }, } - - return paginationFlags(a.viper, cmd, "client states") + cmd = addOutputFlag(a.viper, cmd) + cmd = paginationFlags(a.viper, cmd, "client states") + return cmd } func queryConnections(a *appState) *cobra.Command { @@ -498,7 +539,9 @@ $ %s q conns ibc-1`, }, } - return paginationFlags(a.viper, cmd, "connections on a network") + cmd = addOutputFlag(a.viper, cmd) + cmd = paginationFlags(a.viper, cmd, "connections on a network") + return cmd } func queryConnectionsUsingClient(a *appState) *cobra.Command { @@ -551,7 +594,9 @@ $ %s query client-connections ibc-0 ibczeroclient --height 1205`, }, } - return heightFlag(a.viper, cmd) + cmd = addOutputFlag(a.viper, cmd) + cmd = heightFlag(a.viper, cmd) + return cmd } func queryConnection(a *appState) *cobra.Command { @@ -595,7 +640,7 @@ $ %s q conn ibc-1 ibconeconn`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -644,7 +689,9 @@ $ %s query connection-channels ibc-2 ibcconnection2 --offset 2 --limit 30`, }, } - return paginationFlags(a.viper, cmd, "channels associated with a connection") + cmd = addOutputFlag(a.viper, cmd) + cmd = paginationFlags(a.viper, cmd, "channels associated with a connection") + return cmd } func queryChannel(a *appState) *cobra.Command { @@ -697,7 +744,9 @@ $ %s query channel ibc-2 ibctwochannel transfer --height 1205`, }, } - return heightFlag(a.viper, cmd) + cmd = addOutputFlag(a.viper, cmd) + cmd = heightFlag(a.viper, cmd) + return cmd } // chanExtendedInfo is an intermediate type for holding additional useful @@ -917,7 +966,9 @@ $ %s query channels ibc-0 ibc-2`, }, } - return paginationFlags(a.viper, cmd, "channels on a network") + cmd = addOutputFlag(a.viper, cmd) + cmd = paginationFlags(a.viper, cmd, "channels on a network") + return cmd } func queryPacketCommitment(a *appState) *cobra.Command { @@ -959,7 +1010,7 @@ $ %s q packet-commit ibc-1 ibconechannel transfer 31`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -1012,7 +1063,7 @@ $ %s query unrelayed-pkts demo-path channel-0`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -1064,7 +1115,7 @@ $ %s query unrelayed-acks demo-path channel-0`, return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } @@ -1105,24 +1156,26 @@ $ %s query clients-expiration demo-path`, return errDst } - // if only the src light client is found, just print info for source light client - if errSrc == nil && errDst != nil { - fmt.Fprintln(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[src], srcExpiration, srcClientInfo)) - return nil - } + output, _ := cmd.Flags().GetString(flagOutput) - // if only the dst light client is found, just print info for destination light client - if errDst == nil && errSrc != nil { - fmt.Fprintln(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[dst], dstExpiration, dstClientInfo)) - return nil + srcClientExpiration := relayer.SPrintClientExpiration(c[src], srcExpiration, srcClientInfo) + dstClientExpiration := relayer.SPrintClientExpiration(c[dst], dstExpiration, dstClientInfo) + + if output == formatJson { + srcClientExpiration = relayer.SPrintClientExpirationJson(c[src], srcExpiration, srcClientInfo) + dstClientExpiration = relayer.SPrintClientExpirationJson(c[dst], dstExpiration, dstClientInfo) } - fmt.Fprintln(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[src], srcExpiration, srcClientInfo)) - fmt.Fprintln(cmd.OutOrStdout(), relayer.SPrintClientExpiration(c[dst], dstExpiration, dstClientInfo)) + if errSrc == nil { + fmt.Fprintln(cmd.OutOrStdout(), srcClientExpiration) + } + if errDst == nil { + fmt.Fprintln(cmd.OutOrStdout(), dstClientExpiration) + } return nil }, } - + cmd = addOutputFlag(a.viper, cmd) return cmd } diff --git a/relayer/query.go b/relayer/query.go index 08b2ec1b0..fa59dba14 100644 --- a/relayer/query.go +++ b/relayer/query.go @@ -2,7 +2,9 @@ package relayer import ( "context" + "encoding/json" "fmt" + "strconv" "strings" "time" @@ -308,7 +310,7 @@ func SPrintClientExpiration(chain *Chain, expiration time.Time, clientInfo Clien status = "GOOD" } - return fmt.Sprintf(` + legacyOutput := fmt.Sprintf(` client: %s (%s) HEALTH: %s TIME: %s (%s) @@ -316,4 +318,36 @@ func SPrintClientExpiration(chain *Chain, expiration time.Time, clientInfo Clien TRUSTING PERIOD: %s `, chain.ClientID(), chain.ChainID(), status, expirationFormatted, remainingTime.Round(time.Second), clientInfo.LatestHeight.GetRevisionHeight(), clientInfo.TrustingPeriod.String()) + + return legacyOutput + +} + +// Returns clientExpiration data in JSON format. +func SPrintClientExpirationJson(chain *Chain, expiration time.Time, clientInfo ClientStateInfo) string { + now := time.Now() + remainingTime := expiration.Sub(now) + expirationFormatted := expiration.Format(time.RFC822) + + var status string + if remainingTime <= 0 { + status = "EXPIRED" + } else { + status = "GOOD" + } + + data := map[string]string{ + "client": fmt.Sprintf("%s (%s)", chain.ClientID(), chain.ChainID()), + "HEALTH": status, + "TIME": fmt.Sprintf("%s (%s)", expirationFormatted, remainingTime.Round(time.Second)), + "LAST UPDATE HEIGHT": strconv.FormatUint(clientInfo.LatestHeight.GetRevisionHeight(), 10), + "TRUSTING PERIOD": clientInfo.TrustingPeriod.String(), + } + + jsonOutput, err := json.Marshal(data) + if err != nil { + jsonOutput = []byte{} + } + + return string(jsonOutput) } diff --git a/relayer/query_test.go b/relayer/query_test.go index 39dc837b7..ea7e4b71d 100644 --- a/relayer/query_test.go +++ b/relayer/query_test.go @@ -31,8 +31,10 @@ func TestSPrintClientExpiration_PrintClientId(t *testing.T) { chain := mockChain("test-chain-id", "expected-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, previousTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, previousTime, *clientStateInfo) require.Contains(t, expiration, "expected-client-id") + require.Contains(t, expirationJson, "expected-client-id") } func TestSPrintClientExpiration_PrintExpired_WhenTimeIsInPast(t *testing.T) { @@ -43,8 +45,10 @@ func TestSPrintClientExpiration_PrintExpired_WhenTimeIsInPast(t *testing.T) { chain := mockChain("test-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, previousTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, previousTime, *clientStateInfo) require.Contains(t, expiration, "EXPIRED") + require.Contains(t, expirationJson, "EXPIRED") } func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInPast(t *testing.T) { @@ -55,8 +59,10 @@ func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInPast(t *tes chain := mockChain("expected-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, pastTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, pastTime, *clientStateInfo) require.Contains(t, expiration, pastTime.Format(time.RFC822)) + require.Contains(t, expirationJson, pastTime.Format(time.RFC822)) } func TestSPrintClientExpiration_PrintGood_WhenTimeIsInFuture(t *testing.T) { @@ -67,8 +73,10 @@ func TestSPrintClientExpiration_PrintGood_WhenTimeIsInFuture(t *testing.T) { chain := mockChain("test-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, previousTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, previousTime, *clientStateInfo) require.Contains(t, expiration, "GOOD") + require.Contains(t, expirationJson, "GOOD") } func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInFuture(t *testing.T) { @@ -79,8 +87,10 @@ func TestSPrintClientExpiration_PrintRFC822FormattedTime_WhenTimeIsInFuture(t *t chain := mockChain("test-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, futureTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, futureTime, *clientStateInfo) require.Contains(t, expiration, futureTime.Format(time.RFC822)) + require.Contains(t, expirationJson, futureTime.Format(time.RFC822)) } func TestSPrintClientExpiration_PrintRemainingTime_WhenTimeIsInFuture(t *testing.T) { @@ -91,8 +101,10 @@ func TestSPrintClientExpiration_PrintRemainingTime_WhenTimeIsInFuture(t *testing chain := mockChain("test-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, futureTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, futureTime, *clientStateInfo) require.Contains(t, expiration, "10h0m0s") + require.Contains(t, expirationJson, "10h0m0s") } func TestSPrintClientExpiration_TrustingPeriod(t *testing.T) { @@ -103,8 +115,10 @@ func TestSPrintClientExpiration_TrustingPeriod(t *testing.T) { chain := mockChain("expected-chain-id", "test-client-id") clientStateInfo := mockClientStateInfo("test-chain-id", trustingPeriod, mockHeight) expiration := SPrintClientExpiration(chain, previousTime, *clientStateInfo) + expirationJson := SPrintClientExpirationJson(chain, previousTime, *clientStateInfo) require.Contains(t, expiration, "1h0m0s") + require.Contains(t, expirationJson, "1h0m0s") } func TestSPrintClientExpiration_LastUpdateHeight(t *testing.T) {