Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AI flow boilerplate #3

Merged
merged 3 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .goreleaser.plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ before:
- go mod download

builds:
- id: ai-face
main: cmd/executor/ai-face/main.go
binary: executor_ai-face_{{ .Os }}_{{ .Arch }}

no_unique_dist_dir: true
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- arm64
goarm:
- 7
- id: doctor
main: cmd/executor/doctor/main.go
binary: executor_doctor_{{ .Os }}_{{ .Arch }}
Expand Down Expand Up @@ -84,6 +99,21 @@ builds:
main: cmd/executor/thread-mate/main.go
binary: executor_thread-mate_{{ .Os }}_{{ .Arch }}

no_unique_dist_dir: true
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- arm64
goarm:
- 7
- id: ai-brain
main: cmd/source/ai-brain/main.go
binary: source_ai-brain_{{ .Os }}_{{ .Arch }}

no_unique_dist_dir: true
env:
- CGO_ENABLED=0
Expand Down Expand Up @@ -158,6 +188,12 @@ builds:

archives:

- builds: [ai-face]
id: ai-face
files:
- none*
name_template: "{{ .Binary }}"

- builds: [doctor]
id: doctor
files:
Expand Down Expand Up @@ -194,6 +230,12 @@ archives:
- none*
name_template: "{{ .Binary }}"

- builds: [ai-brain]
id: ai-brain
files:
- none*
name_template: "{{ .Binary }}"

- builds: [argocd]
id: argocd
files:
Expand Down
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ build-plugins: ## Builds all plugins for all defined platforms
goreleaser build -f .goreleaser.plugin.yaml --rm-dist --snapshot
.PHONY: build-plugins

build-plugins-single: ## Builds all plugins only for current GOOS and GOARCH.
goreleaser build -f .goreleaser.plugin.yaml --rm-dist --single-target --snapshot
build-plugins-single: ## Builds specified plugins in binary format only for current GOOS and GOARCH.
go run github.com/kubeshop/botkube/hack/target/build-plugins -plugin-targets=$(PLUGIN_TARGETS) -output-mode=binary -single-platform
.PHONY: build-plugins-single

build-plugins-archives: ## Builds all plugins for all defined platforms in form of arhcives
build-plugins-archives: ## Builds all plugins for all defined platforms in form of archives
goreleaser release -f .goreleaser.plugin.yaml --rm-dist --snapshot
.PHONY: build-plugins

Expand Down Expand Up @@ -39,6 +39,10 @@ fix-lint-issues: ## Automatically fix lint issues
golangci-lint run --fix "./..."
.PHONY: fix-lint-issues

serve-local-plugins: ## Serve local plugins
go run github.com/kubeshop/botkube/hack/target/serve-plugins -plugins-dir=dist
.PHONY: serve-local-plugins

#############
# Others #
#############
Expand Down
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,51 @@ This repository shows Botkube Cloud plugins.

## Development

1. Clone the repository.
2. Follow the [local testing guide](https://docs.botkube.io/plugin/local-testing).
**Prerequisite**

- You are able to start the [Botkube Agent](https://github.com/kubeshop/botkube/blob/main/CONTRIBUTING.md#build-and-run-locally).

**Steps**

1. Start the local plugins server to serve binaries from [`dist`](dist) folder:

```bash
make serve-local-plugins
```

> **Tip**
> If Botkube runs inside the k3d cluster, export the `PLUGIN_SERVER_HOST=http://host.k3d.internal` environment variable.

2. Export the Botkube plugins cache directory:

```bash
export BOTKUBE_PLUGINS_CACHE__DIR="/tmp/plugins"
```

3. Add a `cloud-plugins` entry for your Agent plugins repository:

```yaml
plugins:
repositories:
cloud-plugins:
url: http://localhost:3010/botkube.yaml
```

4. In another terminal window, run:

```bash
# rebuild plugins only for the current GOOS and GOARCH
make build-plugins-single &&
# remove cached plugins
rm -rf $BOTKUBE_PLUGINS_CACHE__DIR &&
# start Botkube to download fresh plugins
./botkube-agent
```

Each time you make a change to the [source](cmd/source) or [executors](cmd/executor) plugins, rerun the above command.

> **Tip**
> To build specific plugin binaries, use `PLUGIN_TARGETS`. For example, `PLUGIN_TARGETS="helm,argocd" make build-plugins-single`.

## Release

Expand Down
13 changes: 13 additions & 0 deletions cmd/executor/ai-face/config_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Echo",
"description": "Echo is an example Botkube executor plugin used during e2e tests. It's not meant for production usage.",
"type": "object",
"properties": {
"aiBrainSourceName": {
"type": "string",
"default": "ai-brain"
}
},
"required": []
}
134 changes: 134 additions & 0 deletions cmd/executor/ai-face/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package main

import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/hashicorp/go-plugin"
aibrain "github.com/kubeshop/botkube-cloud-plugins/internal/source/ai-brain"
"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/httpx"
"github.com/kubeshop/botkube/pkg/loggerx"
pluginx "github.com/kubeshop/botkube/pkg/plugin"
)

var (
// version is set via ldflags by GoReleaser.
version = "dev"

//go:embed config_schema.json
configJSONSchema string
)

const (
pluginName = "ai-face"
description = "Proxies incoming prompts into AI engine a.k.a brain that responds with analysis."
maxRespBodySize = 5 * 1024 * 1024 // 5 MB
)

// Config holds executor configuration.
type Config struct {
AIBrainSourceName string `json:"aiBrainSourceName"`
Log config.Logger `yaml:"log"`
}

var defaultConfig = Config{
AIBrainSourceName: "ai-brain",
}

// AIFace implements Botkube executor plugin.
type AIFace struct {
httpClient *http.Client
}

var _ executor.Executor = &AIFace{}

// Metadata returns details about the plugin.
func (*AIFace) Metadata(context.Context) (api.MetadataOutput, error) {
return api.MetadataOutput{
Version: version,
Description: description,
Recommended: true,
JSONSchema: api.JSONSchema{
Value: configJSONSchema,
},
}, nil
}

// Execute returns a given command as response.
func (e *AIFace) Execute(_ context.Context, in executor.ExecuteInput) (executor.ExecuteOutput, error) {
var cfg Config
err := pluginx.MergeExecutorConfigsWithDefaults(defaultConfig, in.Configs, &cfg)
if err != nil {
return executor.ExecuteOutput{}, fmt.Errorf("while merging input configuration: %w", err)
}

log := loggerx.New(cfg.Log)

aiBrainWebhookURL := fmt.Sprintf("%s/%s", in.Context.IncomingWebhook.BaseSourceURL, cfg.AIBrainSourceName)

body, err := json.Marshal(aibrain.Payload{
Prompt: in.Command,
MessageID: in.Context.Message.ParentActivityID,
})
if err != nil {
return executor.ExecuteOutput{}, fmt.Errorf("failed to marshal payload: %v", err)
}

resp, err := e.httpClient.Post(aiBrainWebhookURL, "application/json", bytes.NewReader(body))
if err != nil {
return executor.ExecuteOutput{}, fmt.Errorf("failed to make HTTP request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
log.Errorf("Failed to dispatch event. Response code: %d", resp.StatusCode)
withLimit := io.LimitReader(resp.Body, maxRespBodySize)
responseBody, err := io.ReadAll(withLimit)
if err != nil {
return executor.ExecuteOutput{}, fmt.Errorf("failed to read response body: %v", err)
}
return executor.ExecuteOutput{}, fmt.Errorf("failed to dispatch event. Response code: %d, Body: %s", resp.StatusCode, responseBody)
}

return executor.ExecuteOutput{
Message: api.Message{
Type: api.SkipMessage,
},
}, nil
}

// Help returns help message
func (*AIFace) Help(context.Context) (api.Message, error) {
btnBuilder := api.NewMessageButtonBuilder()

return api.Message{
Sections: []api.Section{
{
Base: api.Base{
Header: "Botkube: An AI-powered plugin for diagnosing your Kubernetes clusters in natural language.",
},
Buttons: []api.Button{
btnBuilder.ForCommandWithDescCmd("Ask a question", "ai are there any failing pods in my cluster?"),
},
},
},
}, nil
}

func main() {
executor.Serve(map[string]plugin.Plugin{
pluginName: &executor.Plugin{
Executor: &AIFace{
httpClient: httpx.NewHTTPClient(),
},
},
})
}
2 changes: 1 addition & 1 deletion cmd/executor/exec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/formatx"
"github.com/kubeshop/botkube/pkg/loggerx"
"github.com/kubeshop/botkube/pkg/pluginx"
pluginx "github.com/kubeshop/botkube/pkg/plugin"
)

// version is set via ldflags by GoReleaser.
Expand Down
2 changes: 1 addition & 1 deletion cmd/executor/gh/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/loggerx"
"github.com/kubeshop/botkube/pkg/pluginx"
pluginx "github.com/kubeshop/botkube/pkg/plugin"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion cmd/executor/thread-mate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
thmate "github.com/kubeshop/botkube-cloud-plugins/internal/executor/thread-mate"
"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/pluginx"
pluginx "github.com/kubeshop/botkube/pkg/plugin"
)

const pluginName = "thread-mate"
Expand Down
Loading
Loading