diff --git a/runtime/services/autocli.go b/runtime/services/autocli.go index 5f684e5506..e77f15637e 100644 --- a/runtime/services/autocli.go +++ b/runtime/services/autocli.go @@ -4,6 +4,8 @@ import ( "context" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/types/module" ) @@ -22,6 +24,31 @@ func NewAutoCLIQueryService(appModules map[string]module.AppModule) *AutoCLIQuer AutoCLIOptions() *autocliv1.ModuleOptions }); ok { moduleOptions[modName] = autoCliMod.AutoCLIOptions() + } else { + // try to auto-discover options based on the last msg and query + // services registered for the module + cfg := &autocliConfigurator{} + mod.RegisterServices(cfg) + modOptions := &autocliv1.ModuleOptions{} + haveServices := false + + if cfg.msgServer.serviceName != "" { + haveServices = true + modOptions.Tx = &autocliv1.ServiceCommandDescriptor{ + Service: cfg.msgServer.serviceName, + } + } + + if cfg.queryServer.serviceName != "" { + haveServices = true + modOptions.Query = &autocliv1.ServiceCommandDescriptor{ + Service: cfg.queryServer.serviceName, + } + } + + if haveServices { + moduleOptions[modName] = modOptions + } } } return &AutoCLIQueryService{ @@ -35,4 +62,27 @@ func (a AutoCLIQueryService) AppOptions(context.Context, *autocliv1.AppOptionsRe }, nil } +// autocliConfigurator allows us to call RegisterServices and introspect the services +type autocliConfigurator struct { + msgServer autocliServiceRegistrar + queryServer autocliServiceRegistrar +} + +func (a *autocliConfigurator) MsgServer() gogogrpc.Server { return &a.msgServer } + +func (a *autocliConfigurator) QueryServer() gogogrpc.Server { return &a.queryServer } + +func (a *autocliConfigurator) RegisterMigration(string, uint64, module.MigrationHandler) error { + return nil +} + +// autocliServiceRegistrar is used to capture the service name for registered services +type autocliServiceRegistrar struct { + serviceName string +} + +func (a *autocliServiceRegistrar) RegisterService(sd *grpc.ServiceDesc, _ interface{}) { + a.serviceName = sd.ServiceName +} + var _ autocliv1.QueryServer = &AutoCLIQueryService{} diff --git a/tests/integration/runtime/query_test.go b/tests/integration/runtime/query_test.go index 67090215d5..0fa3cdb39f 100644 --- a/tests/integration/runtime/query_test.go +++ b/tests/integration/runtime/query_test.go @@ -127,8 +127,21 @@ func TestQueryAutoCLIAppOptions(t *testing.T) { assert.NilError(t, err) assert.Assert(t, res != nil && res.ModuleOptions != nil) - // make sure we at least have x/auth autocli options + // make sure we have x/auth autocli options which were configured manually authOpts := res.ModuleOptions["auth"] assert.Assert(t, authOpts != nil) - assert.Assert(t, authOpts.Query != nil && authOpts.Query.Service != "") + assert.Assert(t, authOpts.Query != nil) + assert.Equal(t, "cosmos.auth.v1beta1.Query", authOpts.Query.Service) + // make sure we have some custom options + assert.Assert(t, len(authOpts.Query.RpcCommandOptions) != 0) + + // make sure we have x/staking autocli options which should have been auto-discovered + stakingOpts := res.ModuleOptions["staking"] + assert.Assert(t, stakingOpts != nil) + assert.Assert(t, stakingOpts.Query != nil && stakingOpts.Tx != nil) + assert.Equal(t, "cosmos.staking.v1beta1.Query", stakingOpts.Query.Service) + assert.Equal(t, "cosmos.staking.v1beta1.Msg", stakingOpts.Tx.Service) + + // make sure tx module has no autocli options because it has no services + assert.Assert(t, res.ModuleOptions["tx"] == nil) } diff --git a/x/auth/autocli.go b/x/auth/autocli.go new file mode 100644 index 0000000000..302b5f65be --- /dev/null +++ b/x/auth/autocli.go @@ -0,0 +1,32 @@ +package auth + +import ( + authv1beta1 "cosmossdk.io/api/cosmos/auth/v1beta1" + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" +) + +// AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. +func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { + return &autocliv1.ModuleOptions{ + Query: &autocliv1.ServiceCommandDescriptor{ + Service: authv1beta1.Query_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "Account", + Use: "account [address]", + Short: "query account by address", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "address"}}, + }, + { + RpcMethod: "AccountAddressByID", + Use: "address-by-id [acc-num]", + Short: "query account address by account number", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "id"}}, + }, + }, + }, + Tx: &autocliv1.ServiceCommandDescriptor{ + Service: authv1beta1.Msg_ServiceDesc.ServiceName, + }, + } +} diff --git a/x/auth/module.go b/x/auth/module.go index 8cab7194d4..1aacdc004e 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -11,7 +11,6 @@ import ( "cosmossdk.io/depinject" - autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" "cosmossdk.io/core/appmodule" modulev1 "cosmossdk.io/api/cosmos/auth/module/v1" @@ -111,11 +110,6 @@ func (am AppModule) IsOnePerModuleType() {} // IsAppModule implements the appmodule.AppModule interface. func (am AppModule) IsAppModule() {} -// AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. -func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { - return types.AutoCLIOptions -} - // NewAppModule creates a new AppModule object func NewAppModule(cdc codec.Codec, accountKeeper keeper.AccountKeeper, randGenAccountsFn types.RandomGenesisAccountsFn, ss exported.Subspace) AppModule { return AppModule{ diff --git a/x/auth/types/autocli.go b/x/auth/types/autocli.go deleted file mode 100644 index 4257ed4c24..0000000000 --- a/x/auth/types/autocli.go +++ /dev/null @@ -1,26 +0,0 @@ -package types - -import autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" - -var AutoCLIOptions = &autocliv1.ModuleOptions{ - Query: &autocliv1.ServiceCommandDescriptor{ - Service: _Query_serviceDesc.ServiceName, - RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - { - RpcMethod: "Account", - Use: "account [address]", - Short: "query account by address", - PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "address"}}, - }, - { - RpcMethod: "AccountAddressByID", - Use: "address-by-id [id]", - Short: "query account address by account ID", - PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "id"}}, - }, - }, - }, - Tx: &autocliv1.ServiceCommandDescriptor{ - Service: _Msg_serviceDesc.ServiceName, - }, -} diff --git a/x/gov/autocli.go b/x/gov/autocli.go new file mode 100644 index 0000000000..3b53657f79 --- /dev/null +++ b/x/gov/autocli.go @@ -0,0 +1,27 @@ +package gov + +import ( + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + govv1 "cosmossdk.io/api/cosmos/gov/v1" + govv1beta1 "cosmossdk.io/api/cosmos/gov/v1beta1" +) + +// AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. +func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { + return &autocliv1.ModuleOptions{ + Tx: &autocliv1.ServiceCommandDescriptor{ + Service: govv1.Msg_ServiceDesc.ServiceName, + // map v1beta1 as a sub-command + SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{ + "v1beta1": {Service: govv1beta1.Msg_ServiceDesc.ServiceName}, + }, + }, + Query: &autocliv1.ServiceCommandDescriptor{ + Service: govv1.Query_ServiceDesc.ServiceName, + // map v1beta1 as a sub-command + SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{ + "v1beta1": {Service: govv1beta1.Query_ServiceDesc.ServiceName}, + }, + }, + } +} diff --git a/x/gov/module.go b/x/gov/module.go index e8b051c5bf..a5eb112bed 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -17,6 +17,7 @@ import ( modulev1 "cosmossdk.io/api/cosmos/gov/module/v1" "cosmossdk.io/core/appmodule" "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -121,6 +122,12 @@ func (a AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry v1beta1.RegisterInterfaces(registry) } +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + // AppModule implements an application module for the gov module. type AppModule struct { AppModuleBasic