fix: improve hubl errors (#14766)

This commit is contained in:
Julien Robert 2023-01-25 16:04:05 +01:00 committed by GitHub
parent 2b07c7961c
commit 8cc3346ed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 84 deletions

View File

@ -32,14 +32,13 @@ hubl --help
### Add chain
To configure a new chain just run this command using the --init flag and the name of the chain as it's listed in the chain registry (https://github.com/cosmos/chain-registry).
To configure a new chain just run this command using the --init flag and the name of the chain as it's listed in the chain registry (<https://github.com/cosmos/chain-registry>).
If the chain is not listed in the chain registry, you can use any unique name.
```shell
hubl --init [chain-name]
hubl --init regen
hubl init [chain-name]
hubl init regen
```
The chain configuration is stored in `~/.hubl/config.toml`.
@ -56,6 +55,12 @@ endpoint = 'localhost:9090'
insecure = true
```
Or use the `--insecure` flag:
```shell
hubl init regen --insecure
```
:::
### Query

View File

@ -10,8 +10,7 @@ func main() {
panic(err)
}
err = cmd.Execute()
if err != nil {
if err = cmd.Execute(); err != nil {
panic(err)
}
}

View File

@ -19,7 +19,7 @@ import (
// loadFileDescriptorsGRPCReflection attempts to load the file descriptor set using gRPC reflection when cosmos.reflection.v1
// is unavailable.
func loadFileDescriptorsGRPCReflection(ctx context.Context, client *grpc.ClientConn) (*descriptorpb.FileDescriptorSet, error) {
fmt.Printf("This chain does not support cosmos.reflection.v1 yet, we will attempt to use a fallback. Some features may be unsupported and it may not be possible to read all data.\n")
fmt.Printf("This chain does not support cosmos.reflection.v1 yet... attempting to use a fallback. Some features may be unsupported and it may not be possible to read all data.\n")
var interfaceImplNames []string
cosmosReflectBetaClient := reflectionv1beta1.NewReflectionServiceClient(client)
@ -30,9 +30,7 @@ func loadFileDescriptorsGRPCReflection(ctx context.Context, client *grpc.ClientC
InterfaceName: iface,
})
if err == nil {
for _, name := range implRes.ImplementationMessageNames {
interfaceImplNames = append(interfaceImplNames, name)
}
interfaceImplNames = append(interfaceImplNames, implRes.ImplementationMessageNames...)
}
}
}
@ -66,10 +64,9 @@ func loadFileDescriptorsGRPCReflection(ctx context.Context, client *grpc.ClientC
}
}()
err = reflectClient.Send(&grpc_reflection_v1alpha.ServerReflectionRequest{
if err = reflectClient.Send(&grpc_reflection_v1alpha.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1alpha.ServerReflectionRequest_ListServices{},
})
if err != nil {
}); err != nil {
return nil, err
}
@ -95,11 +92,9 @@ func loadFileDescriptorsGRPCReflection(ctx context.Context, client *grpc.ClientC
if err != nil {
return nil, err
}
}
err = reflectClient.CloseSend()
if err != nil {
if err = reflectClient.CloseSend(); err != nil {
return nil, err
}
@ -209,7 +204,7 @@ func addMissingFileDescriptors(ctx context.Context, client *grpc.ClientConn, fdM
}
func guessAutocli(files *protoregistry.Files) *autocliv1.AppOptionsResponse {
fmt.Printf("This chain does not support autocli directly yet. We will use some default mappings in the meantime to support a subset of the available services.\n")
fmt.Printf("This chain does not support autocli directly yet. Using some default mappings in the meantime to support a subset of the available services.\n")
res := map[string]*autocliv1.ModuleOptions{}
files.RangeFiles(func(descriptor protoreflect.FileDescriptor) bool {
services := descriptor.Services()

View File

@ -2,7 +2,6 @@ package internal
import (
"bytes"
"fmt"
"os"
"path"
@ -25,8 +24,8 @@ type GRPCEndpoint struct {
func LoadConfig(configDir string) (*Config, error) {
configPath := configFilename(configDir)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// file doesn't exist
return &Config{Chains: map[string]*ChainConfig{}}, nil
}
@ -36,8 +35,7 @@ func LoadConfig(configDir string) (*Config, error) {
}
config := &Config{}
err = toml.Unmarshal(bz, config)
if err != nil {
if err = toml.Unmarshal(bz, config); err != nil {
return nil, errors.Wrapf(err, "can't load config file: %s", configPath)
}
@ -45,26 +43,21 @@ func LoadConfig(configDir string) (*Config, error) {
}
func SaveConfig(configDir string, config *Config) error {
configPath := configFilename(configDir)
buf := &bytes.Buffer{}
enc := toml.NewEncoder(buf)
err := enc.Encode(config)
if err != nil {
if err := enc.Encode(config); err != nil {
return err
}
err = os.MkdirAll(configDir, 0755)
if err != nil {
if err := os.MkdirAll(configDir, 0755); err != nil {
return err
}
err = os.WriteFile(configPath, buf.Bytes(), 0644)
if err != nil {
configPath := configFilename(configDir)
if err := os.WriteFile(configPath, buf.Bytes(), 0644); err != nil {
return err
}
fmt.Printf("Saved config in %s\n", configPath)
return nil
}

View File

@ -20,16 +20,17 @@ import (
"google.golang.org/protobuf/types/descriptorpb"
)
const DefaultDirName = ".hubl"
const DefaultConfigDirName = ".hubl"
type ChainInfo struct {
client *grpc.ClientConn
ConfigDir string
Chain string
ModuleOptions map[string]*autocliv1.ModuleOptions
ProtoFiles *protoregistry.Files
Context context.Context
Config *ChainConfig
client *grpc.ClientConn
}
func NewChainInfo(configDir string, chain string, config *ChainConfig) *ChainInfo {
@ -91,8 +92,7 @@ func (c *ChainInfo) Load(reload bool) error {
return err
}
err = os.WriteFile(fdsFilename, bz, 0644)
if err != nil {
if err = os.WriteFile(fdsFilename, bz, 0644); err != nil {
return err
}
} else {
@ -101,15 +101,14 @@ func (c *ChainInfo) Load(reload bool) error {
return err
}
err = proto.Unmarshal(bz, fdSet)
if err != nil {
if err = proto.Unmarshal(bz, fdSet); err != nil {
return err
}
}
c.ProtoFiles, err = protodesc.FileOptions{AllowUnresolvable: true}.NewFiles(fdSet)
if err != nil {
return errors.Wrapf(err, "error building protoregistry.Files")
return fmt.Errorf("error building protoregistry.Files: %w", err)
}
appOptsFilename, err := c.appOptsCacheFilename()
@ -165,7 +164,6 @@ func (c *ChainInfo) OpenClient() (*grpc.ClientConn, error) {
var res error
for _, endpoint := range c.Config.GRPCEndpoints {
var err error
var creds credentials.TransportCredentials
if endpoint.Insecure {
creds = insecure.NewCredentials()
@ -174,6 +172,8 @@ func (c *ChainInfo) OpenClient() (*grpc.ClientConn, error) {
MinVersion: tls.VersionTLS12,
})
}
var err error
c.client, err = grpc.Dial(endpoint.Endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
res = multierror.Append(res, err)

View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"github.com/manifoldco/promptui"
)
@ -34,20 +35,40 @@ func GetChainRegistryEntry(chain string) (*ChainRegistryEntry, error) {
}
data := &ChainRegistryEntry{}
err = json.Unmarshal(bz, data)
if err != nil {
if err = json.Unmarshal(bz, data); err != nil {
return nil, err
}
fmt.Printf("Found data for %s in the chain registry\n", chain)
// clean-up the URL
cleanEntries := make([]*APIEntry, 0)
for i, apiEntry := range data.APIs.GRPC {
// clean-up the http(s):// prefix
if strings.Contains(apiEntry.Address, "https://") {
data.APIs.GRPC[i].Address = strings.Replace(apiEntry.Address, "https://", "", 1)
} else if strings.Contains(apiEntry.Address, "http://") {
data.APIs.GRPC[i].Address = strings.Replace(apiEntry.Address, "http://", "", 1)
}
// remove trailing slashes
data.APIs.GRPC[i].Address = strings.TrimSuffix(data.APIs.GRPC[i].Address, "/")
// remove addresses without a port
if !strings.Contains(data.APIs.GRPC[i].Address, ":") {
continue
}
cleanEntries = append(cleanEntries, data.APIs.GRPC[i])
}
data.APIs.GRPC = cleanEntries
fmt.Printf("Found data for %s in the chain registry\n", chain)
return data, nil
}
func SelectGRPCEndpoints(chain string) (string, error) {
entry, err := GetChainRegistryEntry(chain)
if err != nil {
fmt.Printf("Unable to load data for %s in the chain registry. You'll have to specify a gRPC endpoint manually.\n", chain)
fmt.Printf("Unable to load data for %s in the chain registry. Specify a custom gRPC endpoint manually.\n", chain)
prompt := &promptui.Prompt{
Label: "Enter a gRPC endpoint that you trust",
}

View File

@ -13,52 +13,75 @@ import (
"google.golang.org/protobuf/types/dynamicpb"
"cosmossdk.io/client/v2/autocli"
"cosmossdk.io/client/v2/autocli/flag"
)
var (
flagInsecure string = "insecure"
flagUpdate string = "update"
flagConfig string = "config"
)
func RootCommand() (*cobra.Command, error) {
userCfgDir, err := os.UserHomeDir()
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
configDir := path.Join(userCfgDir, DefaultDirName)
configDir := path.Join(homeDir, DefaultConfigDirName)
config, err := LoadConfig(configDir)
if err != nil {
return nil, err
}
var initChain string
cmd := &cobra.Command{
Use: "hubl",
Short: "Hubl is a CLI for interacting with Cosmos SDK chains",
Long: "Hubl is a CLI for interacting with Cosmos SDK chains",
}
// add commands
commands, err := RemoteCommand(config, configDir)
if err != nil {
return nil, err
}
commands = append(commands, InitCommand(config, configDir))
cmd.AddCommand(commands...)
return cmd, nil
}
func InitCommand(config *Config, configDir string) *cobra.Command {
var insecure bool
cmd := &cobra.Command{
Use: "init [foochain]",
Short: "Initialize a new chain",
Long: `To configure a new chain just run this command using the --init flag and the name of the chain as it's listed in the chain registry (https://github.com/cosmos/chain-registry).
If the chain is not listed in the chain registry, you can use any unique name.`,
Example: "hubl --init foochain",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if initChain != "" {
return reconfigure(configDir, initChain, config)
}
chainName := strings.ToLower(args[0])
return cmd.Help()
return reconfigure(cmd, config, configDir, chainName)
},
}
cmd.Flags().StringVar(&initChain, "init", "", "initialize a new chain with the specified name")
cmd.Flags().BoolVar(&insecure, flagInsecure, false, "allow setting up insecure gRPC connection")
return cmd
}
func RemoteCommand(config *Config, configDir string) ([]*cobra.Command, error) {
commands := []*cobra.Command{}
for chain, chainConfig := range config.Chains {
chain, chainConfig := chain, chainConfig
// load chain info
chainInfo := NewChainInfo(configDir, chain, chainConfig)
err = chainInfo.Load(false)
if err != nil {
cmd.AddCommand(&cobra.Command{
Use: chain,
Short: "Unable to load data",
Long: "Unable to load data, reconfiguration needed.",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Error loading chain data for %s: %+v\n", chain, err)
return reconfigure(configDir, chain, config)
},
})
if err := chainInfo.Load(false); err != nil {
commands = append(commands, RemoteErrorCommand(config, configDir, chain, chainConfig, err))
continue
}
@ -77,62 +100,87 @@ If the chain is not listed in the chain registry, you can use any unique name.`,
AddQueryConnFlags: func(command *cobra.Command) {},
}
var update bool
var reconfig bool
var (
update bool
reconfig bool
insecure bool
)
chainCmd := &cobra.Command{
Use: chain,
Short: fmt.Sprintf("Commands for the %s chain", chain),
RunE: func(cmd *cobra.Command, args []string) error {
if reconfig {
return reconfigure(configDir, chain, config)
return reconfigure(cmd, config, configDir, chain)
} else if update {
fmt.Printf("Updating autocli data for %s\n", chain)
chainInfo := NewChainInfo(configDir, chain, chainConfig)
err := chainInfo.Load(true)
return err
cmd.Printf("Updating autocli data for %s\n", chain)
return chainInfo.Load(true)
} else {
return cmd.Help()
}
},
}
chainCmd.Flags().BoolVar(&update, "update", false, "update the CLI commands for the selected chain (should be used after every chain upgrade)")
chainCmd.Flags().BoolVar(&reconfig, "config", false, "re-configure the selected chain (allows choosing a new gRPC endpoint and refreshes data)")
chainCmd.Flags().BoolVar(&update, flagUpdate, false, "update the CLI commands for the selected chain (should be used after every chain upgrade)")
chainCmd.Flags().BoolVar(&reconfig, flagConfig, false, "re-configure the selected chain (allows choosing a new gRPC endpoint and refreshes data")
chainCmd.Flags().BoolVar(&insecure, flagInsecure, false, "allow re-configuring the selected chain using an insecure gRPC connection")
err = appOpts.EnhanceRootCommandWithBuilder(chainCmd, builder)
if err != nil {
if err := appOpts.EnhanceRootCommandWithBuilder(chainCmd, builder); err != nil {
return nil, err
}
cmd.AddCommand(chainCmd)
commands = append(commands, chainCmd)
}
return cmd, nil
return commands, nil
}
func reconfigure(configDir, chain string, config *Config) error {
fmt.Printf("Configuring %s\n", chain)
func RemoteErrorCommand(config *Config, configDir string, chain string, chainConfig *ChainConfig, err error) *cobra.Command {
cmd := &cobra.Command{
Use: chain,
Short: "Unable to load data",
Long: "Unable to load data, reconfiguration needed.",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Printf("Error loading chain data for %s: %+v\n", chain, err)
return reconfigure(cmd, config, configDir, chain)
},
}
cmd.Flags().Bool(flagInsecure, chainConfig.GRPCEndpoints[0].Insecure, "allow setting up insecure gRPC connection")
return cmd
}
func reconfigure(cmd *cobra.Command, config *Config, configDir, chain string) error {
insecure, _ := cmd.Flags().GetBool(flagInsecure)
cmd.Printf("Configuring %s\n", chain)
endpoint, err := SelectGRPCEndpoints(chain)
if err != nil {
return err
}
fmt.Printf("Selected: %s\n", endpoint)
cmd.Printf("%s endpoint selected\n", endpoint)
chainConfig := &ChainConfig{
GRPCEndpoints: []GRPCEndpoint{
{
Endpoint: endpoint,
Insecure: insecure,
},
},
}
config.Chains[chain] = chainConfig
chainInfo := NewChainInfo(configDir, chain, chainConfig)
err = chainInfo.Load(true)
if err != nil {
if err = chainInfo.Load(true); err != nil {
return err
}
return SaveConfig(configDir, config)
config.Chains[chain] = chainConfig
if err := SaveConfig(configDir, config); err != nil {
return err
}
cmd.Printf("Configuration saved to %s\n", configDir)
return nil
}
type dynamicTypeResolver struct {