Repository: vegaprotocol/vegawallet (go)

Issue:
Introduce `network describe` sub-command
That would display the network configuration.

Pull request:
Add network describe subcommand (#336)
closes #336 

The interactive output from describing `fairground` looks as follows:
```
Network
  Name:         fairground
  Address:      127.0.0.1:1789
  Token expiry: 168h0m0s
  Level:        info

API.GRPC
  Retries: 5
  Hosts:
    - n06.testnet.vega.xyz:3007
    - n07.testnet.vega.xyz:3007
    - n08.testnet.vega.xyz:3007
    - n09.testnet.vega.xyz:3007
    - n10.testnet.vega.xyz:3007
    - n11.testnet.vega.xyz:3007
    - n12.testnet.vega.xyz:3007
    - n13.testnet.vega.xyz:3007
    - n14.testnet.vega.xyz:3007
    - n15.testnet.vega.xyz:3007
    - n16.testnet.vega.xyz:3007
    - n17.testnet.vega.xyz:3007
    - n18.testnet.vega.xyz:3007
    - n19.testnet.vega.xyz:3007
    - n20.testnet.vega.xyz:3007

API.REST
  Hosts:
    - https://lb.testnet.vega.xyz/datanode/rest

API.GraphQL
  Hosts:
    - https://lb.testnet.vega.xyz/datanode/gql/query

Console
  Address: console.fairground.wtf:1847
```

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3edd0e..849670f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
 - [300](https://github.com/vegaprotocol/vegawallet/pull/300) - Internal restructuring of CLI building blocks to improve testability
 - [359](https://github.com/vegaprotocol/vegawallet/pull/359) - Add `key describe` subcommand to allow querying of key information
 - [364](https://github.com/vegaprotocol/vegawallet/pull/364) - Add changelog checker Github action
+- [361](https://github.com/vegaprotocol/vegawallet/pull/361) - Add `network describe` subcommand to allow querying of network information
 
 ### Fixes
 - [356](https://github.com/vegaprotocol/vegawallet/pull/356) - Ensure the interactive printer is listening to color management env vars (`NO_COLOR` and `CLICOLOR`)
diff --git a/cmd/command.go b/cmd/command.go
index 6140e71..d1e2c54 100644
--- a/cmd/command.go
+++ b/cmd/command.go
@@ -109,7 +109,7 @@ func BuildCmdSendCommand(w io.Writer, handler SendCommandHandler, rf *RootFlags)
 	cmd.Flags().StringVarP(&f.PubKey,
 		"pubkey", "k",
 		"",
-		"Public key to isolate (hex-encoded)",
+		"Public key of the key pair to use for signing (hex-encoded)",
 	)
 	cmd.Flags().StringVarP(&f.PassphraseFile,
 		"passphrase-file", "p",
diff --git a/cmd/network.go b/cmd/network.go
index 175866a..d8587e0 100644
--- a/cmd/network.go
+++ b/cmd/network.go
@@ -16,5 +16,6 @@ func NewCmdNetwork(w io.Writer, rf *RootFlags) *cobra.Command {
 	// create subcommands
 	cmd.AddCommand(NewCmdListNetworks(w, rf))
 	cmd.AddCommand(NewCmdImportNetwork(w, rf))
+	cmd.AddCommand(NewCmdDescribeNetwork(w, rf))
 	return cmd
 }
diff --git a/cmd/network_describe.go b/cmd/network_describe.go
new file mode 100644
index 0000000..58e9ce3
--- /dev/null
+++ b/cmd/network_describe.go
@@ -0,0 +1,130 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+
+	"code.vegaprotocol.io/shared/paths"
+	"code.vegaprotocol.io/vegawallet/cmd/cli"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/cmd/printer"
+	"code.vegaprotocol.io/vegawallet/network"
+	netstore "code.vegaprotocol.io/vegawallet/network/store/v1"
+	"github.com/spf13/cobra"
+)
+
+var (
+	describeNetworkLong = cli.LongDesc(`
+	    Describe all known information about the specified network.
+	`)
+
+	describeNetworkExample = cli.Examples(`
+		# Describe a network
+		vegawallet network describe --network NETWORK
+	`)
+)
+
+type DescribeNetworkHandler func(*network.DescribeNetworkRequest) (*network.DescribeNetworkResponse, error)
+
+func NewCmdDescribeNetwork(w io.Writer, rf *RootFlags) *cobra.Command {
+	h := func(req *network.DescribeNetworkRequest) (*network.DescribeNetworkResponse, error) {
+		vegaPaths := paths.New(rf.Home)
+
+		netStore, err := netstore.InitialiseStore(vegaPaths)
+		if err != nil {
+			return nil, fmt.Errorf("couldn't initialise networks store: %w", err)
+		}
+
+		return network.DescribeNetwork(netStore, req)
+	}
+
+	return BuildCmdDescribeNetwork(w, h, rf)
+}
+
+type DescribeNetworkFlags struct {
+	Network string
+}
+
+func (f *DescribeNetworkFlags) Validate() (*network.DescribeNetworkRequest, error) {
+	req := &network.DescribeNetworkRequest{}
+
+	if len(f.Network) == 0 {
+		return nil, flags.FlagMustBeSpecifiedError("network")
+	}
+	req.Name = f.Network
+
+	return req, nil
+}
+
+func BuildCmdDescribeNetwork(w io.Writer, handler DescribeNetworkHandler, rf *RootFlags) *cobra.Command {
+	f := &DescribeNetworkFlags{}
+	cmd := &cobra.Command{
+		Use:     "describe",
+		Short:   "Describe the specified network",
+		Long:    describeNetworkLong,
+		Example: describeNetworkExample,
+		RunE: func(_ *cobra.Command, _ []string) error {
+			req, err := f.Validate()
+			if err != nil {
+				return err
+			}
+			resp, err := handler(req)
+			if err != nil {
+				return err
+			}
+
+			switch rf.Output {
+			case flags.InteractiveOutput:
+				PrintDescribeNetworkResponse(w, resp)
+			case flags.JSONOutput:
+				return printer.FprintJSON(w, resp)
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().StringVarP(&f.Network,
+		"network", "n",
+		"",
+		"Network to describe",
+	)
+
+	return cmd
+}
+
+func PrintDescribeNetworkResponse(w io.Writer, resp *network.DescribeNetworkResponse) {
+	p := printer.NewInteractivePrinter(w)
+	p.NextLine().Text("Network").NextLine()
+	p.Text("  Name:         ").WarningText(resp.Name).NextLine()
+	p.Text("  Address:      ").WarningText(resp.Host).Text(":").WarningText(fmt.Sprint(resp.Port)).NextLine()
+	p.Text("  Token expiry: ").WarningText(resp.TokenExpiry).NextLine()
+	p.Text("  Level:        ").WarningText(resp.Level)
+	p.NextSection()
+
+	p.Text("API.GRPC").NextLine()
+	p.Text("  Retries: ").WarningText(fmt.Sprint(resp.API.GRPCConfig.Retries)).NextLine()
+	p.Text("  Hosts:").NextLine()
+	for _, h := range resp.API.GRPCConfig.Hosts {
+		p.Text("    - ").WarningText(h).NextLine()
+	}
+	p.NextLine()
+
+	p.Text("API.REST").NextLine()
+	p.Text("  Hosts:").NextLine()
+	for _, h := range resp.API.RESTConfig.Hosts {
+		p.Text("    - ").WarningText(h).NextLine()
+	}
+
+	p.NextLine()
+	p.Text("API.GraphQL").NextLine()
+	p.Text("  Hosts:").NextLine()
+	for _, h := range resp.API.GraphQLConfig.Hosts {
+		p.Text("    - ").WarningText(h).NextLine()
+	}
+	p.NextLine()
+
+	p.Text("Console").NextLine()
+	p.Text("  Address: ").WarningText(resp.Console.URL).Text(":").WarningText(fmt.Sprint(resp.Console.LocalPort))
+	p.NextSection()
+}
diff --git a/network/handler.go b/network/handler.go
index a1bd8e1..47fe666 100644
--- a/network/handler.go
+++ b/network/handler.go
@@ -154,3 +154,53 @@ func ListNetworks(store Store) (*ListNetworksResponse, error) {
 type ListNetworksResponse struct {
 	Networks []string `json:"networks"`
 }
+
+func DescribeNetwork(store Store, req *DescribeNetworkRequest) (*DescribeNetworkResponse, error) {
+	resp := &DescribeNetworkResponse{}
+	net, err := GetNetwork(store, req.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	resp.Name = net.Name
+	resp.TokenExpiry = net.TokenExpiry.String()
+	resp.Level = net.Level.String()
+	resp.Host = net.Host
+	resp.Port = net.Port
+	resp.API.GRPCConfig.Hosts = net.API.GRPC.Hosts
+	resp.API.GRPCConfig.Retries = net.API.GRPC.Retries
+	resp.API.RESTConfig.Hosts = net.API.REST.Hosts
+	resp.API.GraphQLConfig.Hosts = net.API.GraphQL.Hosts
+	resp.Console.LocalPort = net.Console.LocalPort
+	resp.Console.URL = net.Console.URL
+
+	return resp, nil
+}
+
+type DescribeNetworkRequest struct {
+	Name string
+}
+
+type DescribeNetworkResponse struct {
+	Name        string `json:"name"`
+	Level       string `json:"logLevel"`
+	TokenExpiry string `json:"tokenExpiry"`
+	Port        int    `json:"port"`
+	Host        string `json:"host"`
+	API         struct {
+		GRPCConfig struct {
+			Hosts   []string `json:"hosts"`
+			Retries uint64   `json:"retries"`
+		} `json:"grpcConfig"`
+		RESTConfig struct {
+			Hosts []string `json:"hosts"`
+		} `json:"restConfig"`
+		GraphQLConfig struct {
+			Hosts []string `json:"hosts"`
+		} `json:"graphQLConfig"`
+	} `json:"api"`
+	Console struct {
+		URL       string `json:"url"`
+		LocalPort int    `json:"localPort"`
+	}
+}


Test patch:
diff --git a/cmd/key_describe_test.go b/cmd/key_describe_test.go
new file mode 100644
index 0000000..0fbcab3
--- /dev/null
+++ b/cmd/key_describe_test.go
@@ -0,0 +1,93 @@
+package cmd_test
+
+import (
+	"testing"
+
+	vgrand "code.vegaprotocol.io/shared/libs/rand"
+	"code.vegaprotocol.io/vegawallet/cmd"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/wallet"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDescribeKeyFlags(t *testing.T) {
+	t.Run("Valid flags succeeds", testKeyDescribeValidFlagsSucceeds)
+	t.Run("Missing wallet fails", testKeyMissingWalletFails)
+	t.Run("Missing public key fails", testKeyMissingPublicKeyFails)
+}
+
+func testKeyDescribeValidFlagsSucceeds(t *testing.T) {
+	// given
+	testDir, cleanUpFn := NewTempDir(t)
+	defer cleanUpFn(t)
+
+	// given
+	passphrase, passphraseFilePath := NewPassphraseFile(t, testDir)
+	walletName := vgrand.RandomStr(10)
+	pubKey := vgrand.RandomStr(10)
+
+	f := &cmd.DescribeKeyFlags{
+		Wallet:         walletName,
+		PassphraseFile: passphraseFilePath,
+		PubKey:         pubKey,
+	}
+
+	expectedReq := &wallet.DescribeKeyRequest{
+		Wallet:     walletName,
+		Passphrase: passphrase,
+		PubKey:     pubKey,
+	}
+
+	// when
+	req, err := f.Validate()
+
+	// then
+	require.NoError(t, err)
+	require.NotNil(t, req)
+	assert.Equal(t, expectedReq, req)
+}
+
+func testKeyMissingWalletFails(t *testing.T) {
+	// given
+	testDir, cleanUpFn := NewTempDir(t)
+	defer cleanUpFn(t)
+
+	// given
+	_, passphraseFilePath := NewPassphraseFile(t, testDir)
+	pubKey := vgrand.RandomStr(10)
+
+	f := &cmd.DescribeKeyFlags{
+		PassphraseFile: passphraseFilePath,
+		PubKey:         pubKey,
+	}
+
+	// when
+	req, err := f.Validate()
+
+	// then
+	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("wallet"))
+	require.Nil(t, req)
+}
+
+func testKeyMissingPublicKeyFails(t *testing.T) {
+	// given
+	testDir, cleanUpFn := NewTempDir(t)
+	defer cleanUpFn(t)
+
+	// given
+	_, passphraseFilePath := NewPassphraseFile(t, testDir)
+	walletName := vgrand.RandomStr(10)
+
+	f := &cmd.DescribeKeyFlags{
+		PassphraseFile: passphraseFilePath,
+		Wallet:         walletName,
+	}
+
+	// when
+	req, err := f.Validate()
+
+	// then
+	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("pubkey"))
+	require.Nil(t, req)
+}
diff --git a/cmd/network_describe_test.go b/cmd/network_describe_test.go
new file mode 100644
index 0000000..7c1cfc4
--- /dev/null
+++ b/cmd/network_describe_test.go
@@ -0,0 +1,49 @@
+package cmd_test
+
+import (
+	"testing"
+
+	vgrand "code.vegaprotocol.io/shared/libs/rand"
+	"code.vegaprotocol.io/vegawallet/cmd"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/network"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDescribeNetworkFlags(t *testing.T) {
+	t.Run("Valid flags with Network succeeds", testDescribeNetworkValidFlagsWithNetworkSucceeds)
+	t.Run("Invalid flags without Network fails", testDescribeNetworkInvalidFlagsWithoutNetworkFails)
+}
+
+func testDescribeNetworkValidFlagsWithNetworkSucceeds(t *testing.T) {
+	// given
+	networkName := vgrand.RandomStr(10)
+
+	f := &cmd.DescribeNetworkFlags{
+		Network: networkName,
+	}
+
+	expectedReq := &network.DescribeNetworkRequest{
+		Name: networkName,
+	}
+	// when
+	req, err := f.Validate()
+
+	// then
+	require.NoError(t, err)
+	require.NotNil(t, req)
+	assert.Equal(t, expectedReq, req)
+}
+
+func testDescribeNetworkInvalidFlagsWithoutNetworkFails(t *testing.T) {
+	// given
+	f := &cmd.DescribeNetworkFlags{}
+
+	// when
+	req, err := f.Validate()
+
+	// then
+	assert.ErrorIs(t, err, flags.FlagMustBeSpecifiedError("network"))
+	require.Nil(t, req)
+}
diff --git a/tests/commands_for_test.go b/tests/commands_for_test.go
index a0827d8..8b85b48 100644
--- a/tests/commands_for_test.go
+++ b/tests/commands_for_test.go
@@ -403,6 +403,100 @@ func NetworkList(t *testing.T, args []string) (*ListNetworksResponse, error) {
 	return resp, nil
 }
 
+type DescribeNetworkResponse struct {
+	Name        string `json:"name"`
+	Level       string `json:"logLevel"`
+	TokenExpiry string `json:"tokenExpiry"`
+	Port        int    `json:"port"`
+	Host        string `json:"host"`
+	API         struct {
+		GRPCConfig struct {
+			Hosts   []string `json:"hosts"`
+			Retries uint64   `json:"retries"`
+		} `json:"grpcConfig"`
+		RESTConfig struct {
+			Hosts []string `json:"hosts"`
+		} `json:"restConfig"`
+		GraphQLConfig struct {
+			Hosts []string `json:"hosts"`
+		} `json:"graphQLConfig"`
+	} `json:"api"`
+	Console struct {
+		URL       string `json:"url"`
+		LocalPort int    `json:"localPort"`
+	}
+}
+
+func NetworkDescribe(t *testing.T, args []string) (*DescribeNetworkResponse, error) {
+	t.Helper()
+	argsWithCmd := []string{"network", "describe"}
+	argsWithCmd = append(argsWithCmd, args...)
+	output, err := ExecuteCmd(t, argsWithCmd)
+	if err != nil {
+		return nil, err
+	}
+	resp := &DescribeNetworkResponse{}
+	if err := json.Unmarshal(output, resp); err != nil {
+		t.Fatalf("couldn't unmarshal command output: %v", err)
+	}
+	return resp, nil
+}
+
+type DescribeNetworkAssertion struct {
+	t    *testing.T
+	resp *DescribeNetworkResponse
+}
+
+func AssertDescribeNetwork(t *testing.T, resp *DescribeNetworkResponse) *DescribeNetworkAssertion {
+	t.Helper()
+
+	assert.NotNil(t, resp)
+	assert.NotEmpty(t, resp.Name)
+
+	return &DescribeNetworkAssertion{
+		t:    t,
+		resp: resp,
+	}
+}
+
+func (d *DescribeNetworkAssertion) WithName(expected string) *DescribeNetworkAssertion {
+	assert.Equal(d.t, expected, d.resp.Name)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithHostAndPort(host string, port int) *DescribeNetworkAssertion {
+	assert.Equal(d.t, host, d.resp.Host)
+	assert.Equal(d.t, port, d.resp.Port)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithTokenExpiry(expected string) *DescribeNetworkAssertion {
+	assert.Equal(d.t, expected, d.resp.TokenExpiry)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithConsole(url string, port int) *DescribeNetworkAssertion {
+	assert.Equal(d.t, url, d.resp.Console.URL)
+	assert.Equal(d.t, port, d.resp.Console.LocalPort)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithGRPCConfig(hosts []string, retires uint64) *DescribeNetworkAssertion {
+	assert.Equal(d.t, hosts, d.resp.API.GRPCConfig.Hosts)
+	assert.Equal(d.t, retires, d.resp.API.GRPCConfig.Retries)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithGraphQLConfig(hosts []string) *DescribeNetworkAssertion {
+	assert.Equal(d.t, hosts, d.resp.API.GraphQLConfig.Hosts)
+	return d
+}
+
+func (d *DescribeNetworkAssertion) WithRESTConfig(hosts []string) *DescribeNetworkAssertion {
+	assert.Equal(d.t, hosts, d.resp.API.RESTConfig.Hosts)
+	return d
+}
+
 type SignMessageResponse struct {
 	Signature string `json:"signature"`
 }
diff --git a/tests/helpers_for_test.go b/tests/helpers_for_test.go
index f53f296..d496d1d 100644
--- a/tests/helpers_for_test.go
+++ b/tests/helpers_for_test.go
@@ -45,3 +45,33 @@ func DefaultMetaName(t *testing.T, wallet string, index int) string {
 	t.Helper()
 	return fmt.Sprintf("%s key %d", wallet, index)
 }
+
+func FakeNetwork(name string) string {
+	return fmt.Sprintf(`
+Name = "%s"
+Level = "info"
+TokenExpiry = "1h0m0s"
+Port = 8000
+Host = "127.0.0.1"
+
+[API.GRPC]
+Retries = 5
+Hosts = [
+    "example.com:3007",
+]
+
+[API.REST]
+Hosts = [
+    "https://example.com/rest"
+]
+
+[API.GraphQL]
+Hosts = [
+    "https://example.com/gql/query"
+]
+
+[Console]
+URL = "console.example.com"
+LocalPort = 1847
+`, name)
+}
diff --git a/tests/sc_describe_network_test.go b/tests/sc_describe_network_test.go
new file mode 100644
index 0000000..67d3b4e
--- /dev/null
+++ b/tests/sc_describe_network_test.go
@@ -0,0 +1,56 @@
+package tests_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestDescribeNetwork(t *testing.T) {
+	// given
+	home, cleanUpFn := NewTempDir(t)
+	defer cleanUpFn(t)
+
+	networkFile := NewFile(t, home, "my-network-1.toml", FakeNetwork("my-network-1"))
+
+	cmd := []string{
+		"--home", home,
+		"--output", "json",
+	}
+
+	// when
+	importNetworkResp, err := NetworkImport(t, append(cmd,
+		"--from-file", networkFile,
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertImportNetwork(t, importNetworkResp).
+		WithName("my-network-1").
+		LocatedUnder(home)
+
+	// when
+	describeResp, err := NetworkDescribe(t, append(cmd,
+		"--network", "my-network-1",
+	))
+
+	// then
+	require.NoError(t, err)
+	AssertDescribeNetwork(t, describeResp).
+		WithName("my-network-1").
+		WithHostAndPort("127.0.0.1", 8000).
+		WithTokenExpiry("1h0m0s").
+		WithConsole("console.example.com", 1847).
+		WithGRPCConfig([]string{"example.com:3007"}, 5).
+		WithRESTConfig([]string{"https://example.com/rest"}).
+		WithGraphQLConfig([]string{"https://example.com/gql/query"})
+
+	// when
+	describeResp, err = NetworkDescribe(t, append(cmd,
+		"--network", "i-do-not-exist",
+	))
+
+	// then
+	require.Error(t, err)
+	require.Nil(t, describeResp)
+}
diff --git a/tests/sc_import_network_test.go b/tests/sc_import_network_test.go
index 9e2afe5..a3529d7 100644
--- a/tests/sc_import_network_test.go
+++ b/tests/sc_import_network_test.go
@@ -1,7 +1,6 @@
 package tests_test
 
 import (
-	"fmt"
 	"sort"
 	"testing"
 
@@ -14,7 +13,7 @@ func TestImportNetwork(t *testing.T) {
 	home, cleanUpFn := NewTempDir(t)
 	defer cleanUpFn(t)
 
-	networkFile1 := NewFile(t, home, "my-network-1.toml", fakeNetwork("my-network-1"))
+	networkFile1 := NewFile(t, home, "my-network-1.toml", FakeNetwork("my-network-1"))
 
 	// when
 	importNetworkResp1, err := NetworkImport(t, []string{
@@ -41,7 +40,7 @@ func TestImportNetwork(t *testing.T) {
 	require.Equal(t, []string{"my-network-1"}, listNetsResp1.Networks)
 
 	// given
-	networkFile2 := NewFile(t, home, "my-network-2.toml", fakeNetwork("my-network-2"))
+	networkFile2 := NewFile(t, home, "my-network-2.toml", FakeNetwork("my-network-2"))
 
 	// when
 	importNetworkResp2, err := NetworkImport(t, []string{
@@ -73,7 +72,7 @@ func TestForceImportNetwork(t *testing.T) {
 	home, cleanUpFn := NewTempDir(t)
 	defer cleanUpFn(t)
 
-	networkFile := NewFile(t, home, "my-network.toml", fakeNetwork("my-network"))
+	networkFile := NewFile(t, home, "my-network.toml", FakeNetwork("my-network"))
 
 	// when
 	importNetworkResp1, err := NetworkImport(t, []string{
@@ -130,7 +129,7 @@ func TestImportNetworkWithNewName(t *testing.T) {
 	home, cleanUpFn := NewTempDir(t)
 	defer cleanUpFn(t)
 
-	networkFile := NewFile(t, home, "my-network.toml", fakeNetwork("my-network"))
+	networkFile := NewFile(t, home, "my-network.toml", FakeNetwork("my-network"))
 
 	// when
 	importNetworkResp1, err := NetworkImport(t, []string{
@@ -186,33 +185,3 @@ func TestImportNetworkWithNewName(t *testing.T) {
 	sort.Strings(expectedNets)
 	require.Equal(t, expectedNets, listNetsResp2.Networks)
 }
-
-func fakeNetwork(name string) string {
-	return fmt.Sprintf(`
-Name = "%s"
-Level = "info"
-TokenExpiry = "1h0m0s"
-Port = 8000
-Host = "127.0.0.1"
-
-[API.GRPC]
-Retries = 5
-Hosts = [
-    "example.com:3007",
-]
-
-[API.REST]
-Hosts = [
-    "https://example.com/rest"
-]
-
-[API.GraphQL]
-Hosts = [
-    "https://example.com/gql/query"
-]
-
-[Console]
-URL = "console.example.com"
-LocalPort = 1847
-`, name)
-}