diff --git a/cmd/char/ls.go b/cmd/char/ls.go index 394a85f..505ef87 100644 --- a/cmd/char/ls.go +++ b/cmd/char/ls.go @@ -3,27 +3,42 @@ package char import ( "fmt" + "git.front.kjuulh.io/kjuulh/char/pkg/plugins/provider" "git.front.kjuulh.io/kjuulh/char/pkg/register" "git.front.kjuulh.io/kjuulh/char/pkg/schema" "github.com/spf13/cobra" ) func NewLsCommand() *cobra.Command { + gpp := provider.NewGitPluginProvider() + cmd := &cobra.Command{ Use: "ls", RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - _, err := schema.ParseFile(ctx, ".char.yml") + s, err := schema.ParseFile(ctx, ".char.yml") if err != nil { return err } - r, err := register. - NewPluginRegisterBuilder(). - Add("gocli", "plugins/gocli/main.go"). - Add("rust", "plugins/rust/main.go"). - Build(ctx) + plugins, err := s.GetPlugins(ctx) + if err != nil { + return err + } + + err = gpp.FetchPlugins(ctx, s.Registry, plugins) + if err != nil { + return err + } + + builder := register.NewPluginRegisterBuilder() + + for name, plugin := range plugins { + builder = builder.Add(name.Hash(), plugin.Opts.Path) + } + + r, err := builder.Build(ctx) if err != nil { return err } diff --git a/examples/basic/.char/plugins/gocli b/examples/basic/.char/plugins/gocli deleted file mode 120000 index 11a54ed..0000000 --- a/examples/basic/.char/plugins/gocli +++ /dev/null @@ -1 +0,0 @@ -../../../../ \ No newline at end of file diff --git a/examples/basic/.char/plugins/rust b/examples/basic/.char/plugins/rust deleted file mode 120000 index 11a54ed..0000000 --- a/examples/basic/.char/plugins/rust +++ /dev/null @@ -1 +0,0 @@ -../../../../ \ No newline at end of file diff --git a/examples/basic/.gitignore b/examples/basic/.gitignore new file mode 100644 index 0000000..63dffac --- /dev/null +++ b/examples/basic/.gitignore @@ -0,0 +1 @@ +.char/plugins/ diff --git a/examples/basic/char b/examples/basic/char index 57d0959..091909a 100755 Binary files a/examples/basic/char and b/examples/basic/char differ diff --git a/pkg/plugins/provider/git.go b/pkg/plugins/provider/git.go new file mode 100644 index 0000000..54b4489 --- /dev/null +++ b/pkg/plugins/provider/git.go @@ -0,0 +1,79 @@ +package provider + +import ( + "context" + "fmt" + "log" + "os" + "os/exec" + "strings" + + "git.front.kjuulh.io/kjuulh/char/pkg/schema" + "golang.org/x/sync/errgroup" +) + +type GitPluginProvider struct{} + +func NewGitPluginProvider() *GitPluginProvider { + return &GitPluginProvider{} +} + +func (gpp *GitPluginProvider) FetchPlugins(ctx context.Context, registry string, plugins schema.CharSchemaPlugins) error { + errgroup, ctx := errgroup.WithContext(ctx) + baseDir := ".char/plugins" + if os.Getenv("CHAR_DEV_MODE") == "true" { + if err := os.RemoveAll(baseDir); err != nil { + return err + } + } + if err := os.MkdirAll(baseDir, 0755); err != nil { + return err + } + + for n, plugin := range plugins { + n, plugin := n, plugin + errgroup.Go(func() error { + log.Printf("fetching git plugin repo: %s", n) + return gpp.FetchPlugin( + ctx, + registry, + plugin, + fmt.Sprintf( + "%s/%s", + strings.TrimRight(baseDir, "/"), n.Hash(), + ), + ) + }) + } + + if err := errgroup.Wait(); err != nil { + return err + } + + return nil +} + +func (gpp *GitPluginProvider) FetchPlugin(ctx context.Context, registry string, plugin *schema.CharSchemaPlugin, dest string) error { + cloneUrl, err := plugin.Opts.GetCloneUrl(ctx, registry, &schema.CloneUrlOpt{ + Protocol: schema.GitProtocolSsh, + SshUser: "git", + }, + ) + if err != nil { + return err + } + + output, err := exec.Command( + "git", + "clone", + cloneUrl, + dest, + ).CombinedOutput() + if len(output) > 0 { + log.Print(string(output)) + } + if err != nil { + return err + } + return nil +} diff --git a/pkg/register/plugin_builder.go b/pkg/register/plugin_builder.go index eef9d70..dcbcc38 100644 --- a/pkg/register/plugin_builder.go +++ b/pkg/register/plugin_builder.go @@ -12,7 +12,7 @@ type PluginBuilder struct { serveConfig *plugin.ServeConfig } -func NewPluginBuilder(name string, p Plugin) *PluginBuilder { +func NewPluginBuilder(p Plugin) *PluginBuilder { logger := hclog.New(&hclog.LoggerOptions{ Level: hclog.Error, Output: os.Stderr, @@ -20,7 +20,7 @@ func NewPluginBuilder(name string, p Plugin) *PluginBuilder { }) var pluginMap = map[string]plugin.Plugin{ - name: &PluginAPI{ + "plugin": &PluginAPI{ Impl: p, }, } diff --git a/pkg/register/plugin_register.go b/pkg/register/plugin_register.go index 9d7a698..a00f888 100644 --- a/pkg/register/plugin_register.go +++ b/pkg/register/plugin_register.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/exec" + "strings" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" @@ -43,7 +44,7 @@ func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, er name, p := name, p errgroup.Go(func() error { - pluginPath := fmt.Sprintf(".char/plugins/%s/dist/%s", name, name) + pluginPath := fmt.Sprintf(".char/plugins/%s/dist/cmd", name) _, err := os.Stat(pluginPath) if err != nil || os.Getenv("CHAR_DEV_MODE") == "true" { @@ -52,10 +53,15 @@ func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, er "sh", "-c", fmt.Sprintf( - "(cd .char/plugins/%s; go build -o dist/%s %s)", + "(cd .char/plugins/%s; go build -o dist/plugin %s/main.go)", name, - name, - p.path, + strings.TrimSuffix( + strings.TrimSuffix( + p.path, + "main.go", + ), + "/", + ), ), ) output, err := cmd.CombinedOutput() @@ -80,8 +86,7 @@ func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, er }), Cmd: exec.Command( fmt.Sprintf( - ".char/plugins/%s/dist/%s", - name, + ".char/plugins/%s/dist/plugin", name, ), ), @@ -95,7 +100,7 @@ func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, er return err } - raw, err := rpcClient.Dispense(name) + raw, err := rpcClient.Dispense("plugin") if err != nil { return err } diff --git a/pkg/schema/schema_plugin.go b/pkg/schema/schema_plugin.go index 6f083c9..cd359d3 100644 --- a/pkg/schema/schema_plugin.go +++ b/pkg/schema/schema_plugin.go @@ -2,6 +2,8 @@ package schema import ( "context" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "regexp" @@ -10,6 +12,11 @@ import ( type CharSchemaPluginName string +func (cspn CharSchemaPluginName) Hash() string { + bytes := sha256.Sum256([]byte(cspn)) + return hex.EncodeToString(bytes[:]) +} + type PluginOps struct { Org string RepositoryName string diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go index 2e5187c..5a0cef4 100644 --- a/pkg/schema/schema_test.go +++ b/pkg/schema/schema_test.go @@ -55,16 +55,16 @@ func TestGetPlugins(t *testing.T) { input: ` registry: git.front.kjuulh.io plugins: - "kjuulh/char#plugins/gocli": {} + "kjuulh/char#plugins/gocli@v1.9.0": {} "kjuulh/char#plugins/rust": {} `, expected: map[schema.CharSchemaPluginName]*schema.CharSchemaPlugin{ - "kjuulh/char#plugins/gocli": { + "kjuulh/char#plugins/gocli@v1.9.0": { Opts: &schema.PluginOps{ Org: "kjuulh", RepositoryName: "char", Path: "plugins/gocli", - Version: "", + Version: "v1.9.0", }, }, "kjuulh/char#plugins/rust": { diff --git a/plugins/gocli/main.go b/plugins/gocli/main.go index a9e8c3b..4303811 100644 --- a/plugins/gocli/main.go +++ b/plugins/gocli/main.go @@ -22,7 +22,6 @@ var _ register.Plugin = &GoCliPlugin{} func main() { if err := register. NewPluginBuilder( - "gocli", &GoCliPlugin{}, ). Serve(context.Background()); err != nil { diff --git a/plugins/rust/main.go b/plugins/rust/main.go index 1d136e8..7839670 100644 --- a/plugins/rust/main.go +++ b/plugins/rust/main.go @@ -22,7 +22,6 @@ var _ register.Plugin = &GoCliPlugin{} func main() { if err := register. NewPluginBuilder( - "rust", &GoCliPlugin{}, ). Serve(context.Background()); err != nil {