From 761c291d32bdc627fee60c07cbb5baa93e5aa3db Mon Sep 17 00:00:00 2001 From: Muhammad Abduh Date: Mon, 11 Sep 2023 14:15:25 +0700 Subject: [PATCH] feat: add workflow and goreleaser for plugin provider --- .github/workflows/release.yaml | 6 +- .goreleaser.yml | 2 +- plugins/config.go | 27 ++++++ plugins/providers/cortex/.goreleaser.yml | 71 +++++++++++++++ plugins/providers/manager.go | 108 +++++++++++++++++++++++ 5 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 plugins/config.go create mode 100644 plugins/providers/cortex/.goreleaser.yml create mode 100644 plugins/providers/manager.go diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e5bd92de..c3483085 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - 'v*' + - "v*" jobs: release: @@ -43,10 +43,10 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2.6.1 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser version: latest - args: --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GO_RELEASER_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml index ba3db26e..bc6b9a83 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -67,7 +67,7 @@ brews: - name: siren homepage: "https://github.com/goto/siren" description: "Universal data observability tool." - tap: + repository: owner: goto name: homebrew-taps license: "Apache 2.0" diff --git a/plugins/config.go b/plugins/config.go new file mode 100644 index 00000000..0780e4b8 --- /dev/null +++ b/plugins/config.go @@ -0,0 +1,27 @@ +package plugins + +type Config struct { + PluginPath string `mapstructure:"plugin_path" yaml:"plugin_path" json:"plugin_path" default:"./plugin"` + Plugins map[string]PluginConfig `mapstructure:"plugins" yaml:"plugins" json:"plugins"` +} + +type PluginConfig struct { + Handshake HandshakeConfig `mapstructure:"handshake" yaml:"handshake" json:"handshake"` + ServiceConfig map[string]interface{} `mapstructure:"service_config" yaml:"plugin_config" json:"plugin_config"` +} + +type HandshakeConfig struct { + // ProtocolVersion is the version that clients must match on to + // agree they can communicate. This should match the ProtocolVersion + // set on ClientConfig when using a plugin. + // This field is not required if VersionedPlugins are being used in the + // Client or Server configurations. + ProtocolVersion uint `mapstructure:"protocol_version" yaml:"protocol_version" json:"protocol_version"` + + // MagicCookieKey and value are used as a very basic verification + // that a plugin is intended to be launched. This is not a security + // measure, just a UX feature. If the magic cookie doesn't match, + // we show human-friendly output. + MagicCookieKey string `mapstructure:"magic_cookie_key" yaml:"magic_cookie_key" json:"magic_cookie_key"` + MagicCookieValue string `mapstructure:"magic_cookie_value" yaml:"magic_cookie_value" json:"magic_cookie_value"` +} diff --git a/plugins/providers/cortex/.goreleaser.yml b/plugins/providers/cortex/.goreleaser.yml new file mode 100644 index 00000000..76bcf320 --- /dev/null +++ b/plugins/providers/cortex/.goreleaser.yml @@ -0,0 +1,71 @@ +project_name: "{{ .Env.PLUGIN_NAME }}" +env: + - GORELEASER_CURRENT_TAG={{ .Env.PLUGIN_VERSION }} +release: + draft: true + prerelease: auto + +before: + hooks: + - go mod tidy + - go mod vendor + +builds: + - main: ./main.go + id: "{{ .Env.PLUGIN_NAME }}" + binary: "{{ .Env.PLUGIN_NAME }}" + flags: + - -a + ldflags: + - -s -w -X github.com/goto/siren/plugins/providers/{{ .Env.PLUGIN_DIR_NAME }}/config.Version={{{{ .Env.PLUGIN_VERSION }}}} -X github.com/goto/siren/plugins/providers/{{ .Env.PLUGIN_DIR_NAME }}/config.BuildCommit={{.FullCommit}} -X github.com/goto/siren/plugins/providers/{{ .Env.PLUGIN_DIR_NAME }}/config.BuildDate={{.Date}} + env: + - CGO_ENABLED=0 + goos: [darwin, linux, windows] + goarch: [amd64, '386'] + +archives: + - name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else if eq .Arch "windows" }}windows + {{- else if eq .Arch "linux" }}linux + {{- else if eq .Arch "darwin" }}macos + {{- else }}{{ .Arch }}{{ end }} + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^build:" + - "Merge pull request" + - "Merge branch" + +checksum: + name_template: "checksums.txt" + +snapshot: + name_template: "{{ .Env.PLUGIN_VERSION }}-next" + +brews: + - name: siren-cortex + homepage: "https://github.com/goto/siren/plugins/providers/{{ .Env.PLUGIN_DIR_NAME }}/" + description: "{{ .Env.PLUGIN_DIR_NAME }} plugin for siren." + repository: + owner: goto + name: homebrew-taps + license: "Apache 2.0" + folder: Formula + dependencies: + - name: git + install: |- + bin.install "{{ .Env.PLUGIN_NAME }}" + commit_author: + name: github-actions[bot] + email: 41898282+github-actions[bot]@users.noreply.github.com \ No newline at end of file diff --git a/plugins/providers/manager.go b/plugins/providers/manager.go new file mode 100644 index 00000000..66b34b64 --- /dev/null +++ b/plugins/providers/manager.go @@ -0,0 +1,108 @@ +package providers + +import ( + "context" + "encoding/json" + "fmt" + "os/exec" + "path/filepath" + + "github.com/goto/salt/log" + "github.com/goto/siren/plugins" + "github.com/hashicorp/go-plugin" +) + +type PluginManager struct { + cfg plugins.Config + pluginClients map[string]*plugin.Client +} + +func NewPluginManager(logger log.Logger, cfg plugins.Config) *PluginManager { + return &PluginManager{ + cfg: cfg, + } +} + +func (pl *PluginManager) InitClients() map[string]*plugin.Client { + var pluginClients = make(map[string]*plugin.Client, 0) + for k, v := range pl.cfg.Plugins { + // We're a host. Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: plugin.HandshakeConfig{ + ProtocolVersion: v.Handshake.ProtocolVersion, + MagicCookieKey: v.Handshake.MagicCookieKey, + MagicCookieValue: v.Handshake.MagicCookieValue, + }, + Cmd: exec.Command("sh", "-c", filepath.Join(pl.cfg.PluginPath, k)), + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + VersionedPlugins: map[int]plugin.PluginSet{ + 1: { + k: &plugins.ProviderV1beta1GRPCPlugin{}, + }, + }, + }) + pluginClients[k] = client + } + + pl.pluginClients = pluginClients + + return pl.pluginClients +} + +func (pl *PluginManager) DispenseClients(pluginClients map[string]*plugin.Client) (map[string]plugins.ProviderV1beta1, error) { + var providerPlugins = make(map[string]plugins.ProviderV1beta1, 0) + + for k, client := range pluginClients { + prot := client.Protocol() + _ = prot + rpcClient, err := client.Client() + if err != nil { + return nil, fmt.Errorf("error creating plugin client: %s with error %s", k, err.Error()) + } else { + // Request the plugin + raw, err := rpcClient.Dispense(k) + if err != nil { + return nil, fmt.Errorf("error dispensing plugin client: %s with error %s", k, err.Error()) + } + + providerClient := raw.(plugins.ProviderV1beta1) + + providerPlugins[k] = providerClient + } + } + + return providerPlugins, nil +} + +func (pl *PluginManager) InitConfigs(ctx context.Context, providerPlugins map[string]plugins.ProviderV1beta1, logLevel string) error { + for k, client := range providerPlugins { + pluginConfig, ok := pl.cfg.Plugins[k] + if !ok { + return fmt.Errorf("cannot found config for provider %s", k) + } + + var serviceConfig = make(map[string]interface{}, 0) + + if pluginConfig.ServiceConfig != nil { + serviceConfig = pluginConfig.ServiceConfig + } + + jsonRaw, err := json.Marshal(serviceConfig) + if err != nil { + return fmt.Errorf("cannot stringify config for provider %s with error %s", k, err.Error()) + } + + if err := client.SetConfig(ctx, string(jsonRaw)); err != nil { + return fmt.Errorf("cannot set config for provider %s with error %s", k, err.Error()) + } + } + return nil +} + +func (pl *PluginManager) Stop() { + for _, pluginClient := range pl.pluginClients { + if !pluginClient.Exited() { + pluginClient.Kill() + } + } +}