Skip to content

Commit

Permalink
feat: add bidirectional communication to plugin system (#3544)
Browse files Browse the repository at this point in the history
* chore: rename `types.proto`to `interface.proto`

* feat: add code analizer support

Adds initial analizer support to allow bidirectional communication
between the CLI and the plugins.

The analizer features should be defined and implemented on top of these
changes.

* feat: add analizer support to plugin's interface

* chore: add proto generated code

* chore: generate interface mocks

* refactor: change protocol implementation to support analizer

* chore: fix issue with missing return

* refactor: change plugin command to add analizer to calls

* feat: add proto types required for the cosmos/proto analysis packages

These types allows the implementation of the dependencies analyzer.

* chore: update plugin template to include analizer arguments

* fix: correct type in Analyzer name

* chore: update plugin documentation

* chore: rename analyzer file name because of typo

* fix: correct typo for analyzer names

* chore: update changelog

* test: fix plugin integration test

* test: fix plugin cmd unit tests

* test: fix plugin service unit test

* ci: fix unused argument warning

* refactor: Add basic GetChainInfo method to plugin API (#3561)

* refactor: Analyzer/analizer -> ClientAPI

* refactor: rename proto files and rebuild

* refactor: Add json tags

* wip/refactor: Module analysis

* feat: Add chain reference to plugin ClientAPI

* feat: Complete Dependencies ClientAPI method

* fix: Address review comments

* feat: Remove services/chain dep from pkg/cosmosanalysis as per discussion

* wip: remove deptools install

* feat: package-specific includes

* fix: Replace Module List call with Chain Info call

* chore: Remove chain analysis code

* feat: ChainInfo API example template

* chore: Update template cli reference

* chore: clean up PR

* fix: Address review comments

* fix: address review comments

* fix: address review comments

* fix: Tests and linting

* chore: add changelog

* fix: linting issues

* tests: fix issue with client api in plugin tests

* tests: fix plugin template for integration tests

---------

Co-authored-by: jeronimoalbi <[email protected]>

* chore: correct typos and simplify code

* chore: update pseudo version for plugin's `go.mod` template

* chore: fix typos

* fix: correct call to module dependency resolution

* chore: remove proto file comment

---------

Co-authored-by: Danilo Pantani <[email protected]>
Co-authored-by: Clockwork <[email protected]>
  • Loading branch information
3 people authored Oct 19, 2023
1 parent 1ea85a3 commit bf692a5
Show file tree
Hide file tree
Showing 38 changed files with 1,275 additions and 428 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Features

- [#3544](https://github.com/ignite/cli/pull/3544) Add bidirectional communication to plugin system
- [#3561](https://github.com/ignite/cli/pull/3561) Add GetChainInfo method to plugin system API

### Changes

- [#3529](https://github.com/ignite/cli/pull/3529) Refactor plugin system to use gRPC
Expand Down
20 changes: 12 additions & 8 deletions docs/docs/apps/02-developing-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,30 @@ type Interface interface {
// Execute will be invoked by ignite when an app Command is executed.
// It is global for all commands declared in Manifest, if you have declared
// multiple commands, use cmd.Path to distinguish them.
Execute(context.Context, *ExecutedCommand) error
// The ClientAPI argument can be used by plugins to get chain app analysis info.
Execute(context.Context, *ExecutedCommand, ClientAPI) error

// ExecuteHookPre is invoked by ignite when a command specified by the Hook
// path is invoked.
// It is global for all hooks declared in Manifest, if you have declared
// multiple hooks, use hook.Name to distinguish them.
ExecuteHookPre(context.Context, *ExecutedHook) error
// The ClientAPI argument can be used by plugins to get chain app analysis info.
ExecuteHookPre(context.Context, *ExecutedHook, ClientAPI) error

// ExecuteHookPost is invoked by ignite when a command specified by the hook
// path is invoked.
// It is global for all hooks declared in Manifest, if you have declared
// multiple hooks, use hook.Name to distinguish them.
ExecuteHookPost(context.Context, *ExecutedHook) error
// The ClientAPI argument can be used by plugins to get chain app analysis info.
ExecuteHookPost(context.Context, *ExecutedHook, ClientAPI) error

// ExecuteHookCleanUp is invoked by ignite when a command specified by the
// hook path is invoked. Unlike ExecuteHookPost, it is invoked regardless of
// execution status of the command and hooks.
// It is global for all hooks declared in Manifest, if you have declared
// multiple hooks, use hook.Name to distinguish them.
ExecuteHookCleanUp(context.Context, *ExecutedHook) error
// The ClientAPI argument can be used by plugins to get chain app analysis info.
ExecuteHookCleanUp(context.Context, *ExecutedHook, ClientAPI) error
}
```

Expand Down Expand Up @@ -155,7 +159,7 @@ To update the app execution, you have to change the `Execute` command. For
example:

```go
func (app) Execute(_ context.Context, cmd *plugin.ExecutedCommand) error {
func (app) Execute(_ context.Context, cmd *plugin.ExecutedCommand, _ plugin.ClientAPI) error {
if len(cmd.Args) == 0 {
return fmt.Errorf("oracle name missing")
}
Expand Down Expand Up @@ -217,7 +221,7 @@ func (app) Manifest(context.Context) (*plugin.Manifest, error) {
}, nil
}

func (app) ExecuteHookPre(_ context.Context, h *plugin.ExecutedHook) error {
func (app) ExecuteHookPre(_ context.Context, h *plugin.ExecutedHook, _ plugin.ClientAPI) error {
switch h.Hook.GetName() {
case "my-hook":
fmt.Println("I'm executed before ignite chain build")
Expand All @@ -227,7 +231,7 @@ func (app) ExecuteHookPre(_ context.Context, h *plugin.ExecutedHook) error {
return nil
}

func (app) ExecuteHookPost(_ context.Context, h *plugin.ExecutedHook) error {
func (app) ExecuteHookPost(_ context.Context, h *plugin.ExecutedHook, _ plugin.ClientAPI) error {
switch h.Hook.GetName() {
case "my-hook":
fmt.Println("I'm executed after ignite chain build (if no error)")
Expand All @@ -237,7 +241,7 @@ func (app) ExecuteHookPost(_ context.Context, h *plugin.ExecutedHook) error {
return nil
}

func (app) ExecuteHookCleanUp(_ context.Context, h *plugin.ExecutedHook) error {
func (app) ExecuteHookCleanUp(_ context.Context, h *plugin.ExecutedHook, _ plugin.ClientAPI) error {
switch h.Hook.GetName() {
case "my-hook":
fmt.Println("I'm executed after ignite chain build (regardless errors)")
Expand Down
29 changes: 24 additions & 5 deletions ignite/cmd/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ func linkPluginHook(rootCmd *cobra.Command, p *plugin.Plugin, hook *plugin.Hook)
}
}

err := p.Interface.ExecuteHookPre(ctx, newExecutedHook(hook, cmd, args))
execHook := newExecutedHook(hook, cmd, args)
c, err := newChainWithHomeFlags(cmd)
if err != nil {
return err
}

Check warning on line 213 in ignite/cmd/plugin.go

View check run for this annotation

Codecov / codecov/patch

ignite/cmd/plugin.go#L212-L213

Added lines #L212 - L213 were not covered by tests
err = p.Interface.ExecuteHookPre(ctx, execHook, plugin.NewClientAPI(c))
if err != nil {
return fmt.Errorf("app %q ExecuteHookPre() error: %w", p.Path, err)
}
Expand All @@ -221,7 +226,12 @@ func linkPluginHook(rootCmd *cobra.Command, p *plugin.Plugin, hook *plugin.Hook)
// if the command has failed the `PostRun` will not execute. here we execute the cleanup step before returnning.
if err != nil {
ctx := cmd.Context()
err := p.Interface.ExecuteHookCleanUp(ctx, newExecutedHook(hook, cmd, args))
execHook := newExecutedHook(hook, cmd, args)
c, err := newChainWithHomeFlags(cmd)
if err != nil {
return err
}
err = p.Interface.ExecuteHookCleanUp(ctx, execHook, plugin.NewClientAPI(c))

Check warning on line 234 in ignite/cmd/plugin.go

View check run for this annotation

Codecov / codecov/patch

ignite/cmd/plugin.go#L228-L234

Added lines #L228 - L234 were not covered by tests
if err != nil {
cmd.Printf("app %q ExecuteHookCleanUp() error: %v", p.Path, err)
}
Expand All @@ -238,8 +248,13 @@ func linkPluginHook(rootCmd *cobra.Command, p *plugin.Plugin, hook *plugin.Hook)
ctx := cmd.Context()
execHook := newExecutedHook(hook, cmd, args)

c, err := newChainWithHomeFlags(cmd)
if err != nil {
return err
}

Check warning on line 254 in ignite/cmd/plugin.go

View check run for this annotation

Codecov / codecov/patch

ignite/cmd/plugin.go#L253-L254

Added lines #L253 - L254 were not covered by tests

defer func() {
err := p.Interface.ExecuteHookCleanUp(ctx, execHook)
err := p.Interface.ExecuteHookCleanUp(ctx, execHook, plugin.NewClientAPI(c))
if err != nil {
cmd.Printf("app %q ExecuteHookCleanUp() error: %v", p.Path, err)
}
Expand All @@ -253,7 +268,7 @@ func linkPluginHook(rootCmd *cobra.Command, p *plugin.Plugin, hook *plugin.Hook)
}
}

err := p.Interface.ExecuteHookPost(ctx, execHook)
err = p.Interface.ExecuteHookPost(ctx, execHook, plugin.NewClientAPI(c))
if err != nil {
return fmt.Errorf("app %q ExecuteHookPost() error : %w", p.Path, err)
}
Expand Down Expand Up @@ -325,7 +340,11 @@ func linkPluginCmd(rootCmd *cobra.Command, p *plugin.Plugin, pluginCmd *plugin.C
}
execCmd.ImportFlags(cmd)
// Call the plugin Execute
err := p.Interface.Execute(ctx, execCmd)
c, err := newChainWithHomeFlags(cmd)
if err != nil {
return err
}

Check warning on line 346 in ignite/cmd/plugin.go

View check run for this annotation

Codecov / codecov/patch

ignite/cmd/plugin.go#L345-L346

Added lines #L345 - L346 were not covered by tests
err = p.Interface.Execute(ctx, execCmd, plugin.NewClientAPI(c))
// NOTE(tb): This pause gives enough time for go-plugin to sync the
// output from stdout/stderr of the plugin. Without that pause, this
// output can be discarded and not printed in the user console.
Expand Down
13 changes: 7 additions & 6 deletions ignite/cmd/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ func TestLinkPluginCmds(t *testing.T) {
fmt.Println(cmd.Use == execCmd.Use, cmd.Use, execCmd.Use)
return cmd.Use == execCmd.Use
}),
mock.Anything,
).
Run(func(_ context.Context, execCmd *plugin.ExecutedCommand) {
Run(func(_ context.Context, execCmd *plugin.ExecutedCommand, _ plugin.ClientAPI) {
// Assert execCmd is populated correctly
assert.True(t, strings.HasSuffix(execCmd.Path, cmd.Use), "wrong path %s", execCmd.Path)
assert.Equal(t, args, execCmd.Args)
Expand Down Expand Up @@ -418,8 +419,8 @@ func TestLinkPluginHooks(t *testing.T) {
hook.PlaceHookOn == execHook.Hook.PlaceHookOn
})
}
asserter := func(hook *plugin.Hook) func(_ context.Context, hook *plugin.ExecutedHook) {
return func(_ context.Context, execHook *plugin.ExecutedHook) {
asserter := func(hook *plugin.Hook) func(_ context.Context, hook *plugin.ExecutedHook, _ plugin.ClientAPI) {
return func(_ context.Context, execHook *plugin.ExecutedHook, _ plugin.ClientAPI) {
assert.True(t, strings.HasSuffix(execHook.ExecutedCommand.Path, hook.PlaceHookOn), "wrong path %q want %q", execHook.ExecutedCommand.Path, hook.PlaceHookOn)
assert.Equal(t, args, execHook.ExecutedCommand.Args)
assertFlags(t, expectedFlags, execHook.ExecutedCommand)
Expand All @@ -429,7 +430,7 @@ func TestLinkPluginHooks(t *testing.T) {
var lastPre *mock.Call
for _, hook := range hooks {
pre := p.EXPECT().
ExecuteHookPre(ctx, matcher(hook)).
ExecuteHookPre(ctx, matcher(hook), mock.Anything).
Run(asserter(hook)).
Return(nil).
Call
Expand All @@ -440,12 +441,12 @@ func TestLinkPluginHooks(t *testing.T) {
}
for _, hook := range hooks {
post := p.EXPECT().
ExecuteHookPost(ctx, matcher(hook)).
ExecuteHookPost(ctx, matcher(hook), mock.Anything).
Run(asserter(hook)).
Return(nil).
Call
cleanup := p.EXPECT().
ExecuteHookCleanUp(ctx, matcher(hook)).
ExecuteHookCleanUp(ctx, matcher(hook), mock.Anything).
Run(asserter(hook)).
Return(nil).
Call
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosanalysis/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func resolveCosmosPackagePath(chainRoot string) (string, error) {
return "", err
}

deps, err := gomodule.ResolveDependencies(modFile)
deps, err := gomodule.ResolveDependencies(modFile, false)
if err != nil {
return "", err
}
Expand Down
37 changes: 19 additions & 18 deletions ignite/pkg/cosmosanalysis/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,57 +22,58 @@ type Msgs map[string][]string
// Module keeps metadata about a Cosmos SDK module.
type Module struct {
// Name of the module.
Name string
Name string `json:"name,omitempty"`

// GoModulePath of the app where the module is defined.
GoModulePath string
GoModulePath string `json:"go_module_path,omitempty"`

// Pkg holds the proto package info.
Pkg protoanalysis.Package
Pkg protoanalysis.Package `json:"package,omitempty"`

// Msg is a list of sdk.Msg implementation of the module.
Msgs []Msg
// Msgs is a list of sdk.Msg implementation of the module.
Msgs []Msg `json:"messages,omitempty"`

// HTTPQueries is a list of module queries.
HTTPQueries []HTTPQuery
HTTPQueries []HTTPQuery `json:"http_queries,omitempty"`

// Types is a list of proto types that might be used by module.
Types []Type
Types []Type `json:"types,omitempty"`
}

// Msg keeps metadata about an sdk.Msg implementation.
type Msg struct {
// Name of the type.
Name string
Name string `json:"name,omitempty"`

// URI of the type.
URI string
URI string `json:"uri,omitempty"`

// FilePath is the path of the .proto file where message is defined at.
FilePath string
// FilePath is the path of the proto file where message is defined.
FilePath string `json:"file_path,omitempty"`
}

// HTTPQuery is an sdk Query.
type HTTPQuery struct {
// Name of the RPC func.
Name string
Name string `json:"name,omitempty"`

// FullName of the query with service name and rpc func name.
FullName string
FullName string `json:"full_name,omitempty"`

// HTTPAnnotations keeps info about http annotations of query.
Rules []protoanalysis.HTTPRule
// Rules keeps info about configured HTTP rules of RPC functions.
Rules []protoanalysis.HTTPRule `json:"rules,omitempty"`

// Paginated indicates that the query is using pagination.
Paginated bool
Paginated bool `json:"paginated,omitempty"`
}

// Type is a proto type that might be used by module.
type Type struct {
Name string
// Name of the type.
Name string `json:"name,omitempty"`

// FilePath is the path of the .proto file where message is defined at.
FilePath string
FilePath string `json:"file_path,omitempty"`
}

type moduleDiscoverer struct {
Expand Down
2 changes: 1 addition & 1 deletion ignite/pkg/cosmosclient/mocks/account_retriever.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ignite/pkg/cosmosclient/mocks/bank_query_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions ignite/pkg/cosmosclient/mocks/faucet_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions ignite/pkg/cosmosclient/mocks/gasometer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ignite/pkg/cosmosclient/mocks/rpc_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion ignite/pkg/cosmosclient/mocks/signer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bf692a5

Please sign in to comment.