diff --git a/.github/workflows/ai-assistant-reconfigure.yml b/.github/workflows/ai-assistant-reconfigure.yml deleted file mode 100644 index bab2ec86..00000000 --- a/.github/workflows/ai-assistant-reconfigure.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Reconfigure OpenAI assistant - -on: - push: - tags: - - '*' - branches: - - main - -jobs: - reconfigure-ai-assistant: - name: Reconfigure OpenAI assistant - runs-on: ubuntu-22.04 - timeout-minutes: 15 - env: - OPENAI_API_KEY: ${{ startsWith(github.ref, 'refs/tags/') && secrets.PROD_OPENAI_API_KEY || secrets.DEV_OPENAI_API_KEY }} - ASSISTANT_ENV: ${{ startsWith(github.ref, 'refs/tags/') && 'prod' || 'dev' }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: volta-cli/action@v4 - - - name: Reconfigure assistant - run: | - cd ./hack/assistant-setup - npm install - npm start diff --git a/.github/workflows/branch-pr-build.yml b/.github/workflows/branch-pr-build.yml index 3dc2444e..1ae571bf 100644 --- a/.github/workflows/branch-pr-build.yml +++ b/.github/workflows/branch-pr-build.yml @@ -86,57 +86,3 @@ jobs: run: | make build-plugins-archives USE_ARCHIVE=true make gen-plugin-index - - release-latest-plugins: - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' - name: Build and release latest plugins - timeout-minutes: 60 - runs-on: ubuntu-latest - needs: [lint, test] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: GCP auth - uses: 'google-github-actions/auth@v2' - with: - credentials_json: ${{ secrets.CLOUD_PLUGINS_LATEST_BUCKET_CREDS }} - - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v2' - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - cache: true - - - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v5 - with: - install-only: true - version: latest - - - name: Build plugins and generate plugins index.yaml - env: - PLUGIN_DOWNLOAD_URL_BASE_PATH: "" - run: | - make build-plugins-archives - USE_ARCHIVE=true make gen-dev-plugin-index - - - name: Upload plugins to GCS - uses: google-github-actions/upload-cloud-storage@v2 - with: - path: 'dist' - destination: '${{ env.BUCKET_NAME }}/' - glob: '*.tar.gz' - parent: false - - - name: Upload plugin index to GCS - uses: google-github-actions/upload-cloud-storage@v2 - with: - path: 'plugins-index.yaml' - destination: '${{ env.BUCKET_NAME }}/' - - - name: 'Disable GCS caching' - run: 'gsutil -m setmeta -h "Cache-Control: no-cache, no-store" gs://${{ env.BUCKET_NAME }}/*' diff --git a/.github/workflows/tag-build.yml b/.github/workflows/tag-build.yml index d843d172..d5c0f2ae 100644 --- a/.github/workflows/tag-build.yml +++ b/.github/workflows/tag-build.yml @@ -7,6 +7,7 @@ on: env: BUCKET_NAME: botkube-cloud-plugins + GITHUB_TOKEN: ${{ secrets.RELEASE_GH_DEV_ACCOUNT_PAT }} VERSION: "${{ github.ref_name }}" jobs: @@ -20,14 +21,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: GCP auth - uses: 'google-github-actions/auth@v2' - with: - credentials_json: ${{ secrets.CLOUD_PLUGINS_BUCKET_CREDS }} - - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v2' - - name: Set up Go uses: actions/setup-go@v5 with: @@ -44,19 +37,9 @@ jobs: env: PLUGIN_DOWNLOAD_URL_BASE_PATH: "${{ env.VERSION }}" run: | - make build-plugins-archives + make publish-plugins USE_ARCHIVE=true make gen-plugin-index - - name: Upload plugins to GCS - uses: google-github-actions/upload-cloud-storage@v2 - with: - path: 'dist' - destination: '${{ env.BUCKET_NAME }}/${{ env.VERSION }}' - glob: '*.tar.gz' - parent: false - - - name: Upload plugin index to GCS - uses: google-github-actions/upload-cloud-storage@v2 - with: - path: 'plugins-index.yaml' - destination: '${{ env.BUCKET_NAME }}/${{ env.VERSION }}' + - name: Upload plugins index + run: | + gh release upload ${{ env.VERSION }} ./plugins-index.yaml --clobber diff --git a/.goreleaser.plugin.tpl.yaml b/.goreleaser.plugin.tpl.yaml index 2f7e2459..20c7bdbf 100644 --- a/.goreleaser.plugin.tpl.yaml +++ b/.goreleaser.plugin.tpl.yaml @@ -32,3 +32,11 @@ archives: snapshot: name_template: 'v{{ .Version }}' + +release: + # If set to true, will not auto-publish the release. + draft: true + prerelease: auto + +changelog: + skip: false diff --git a/.goreleaser.plugin.yaml b/.goreleaser.plugin.yaml index c795faae..292b6a04 100644 --- a/.goreleaser.plugin.yaml +++ b/.goreleaser.plugin.yaml @@ -242,3 +242,11 @@ archives: snapshot: name_template: 'v{{ .Version }}' + +release: + # If set to true, will not auto-publish the release. + draft: true + prerelease: auto + +changelog: + skip: false diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3ad02909 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Kubeshop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 81c9f7c9..43529cd6 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,12 @@ build-plugins-single: ## Builds specified plugins in binary format only for curr build-plugins-archives: ## Builds all plugins for all defined platforms in form of archives goreleaser release -f .goreleaser.plugin.yaml --clean --snapshot -.PHONY: build-plugins +.PHONY: build-plugins-archives + +publish-plugins: ## Builds all plugins for all defined platforms in form of archives + goreleaser release -f .goreleaser.plugin.yaml +.PHONY: publish-plugins + ############## # Generating # diff --git a/README.md b/README.md index 40309a1f..dac3c821 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Botkube Cloud Plugins +# Botkube Plugins -This repository shows Botkube Cloud plugins. +This repository contains additional Botkube plugins. ## Requirements @@ -31,12 +31,12 @@ This repository shows Botkube Cloud plugins. export BOTKUBE_PLUGINS_CACHE__DIR="/tmp/plugins" ``` -3. Add a `cloud-plugins` entry for your Agent plugins repository: +3. Add a `additional-plugins` entry for your Agent plugins repository: ```yaml plugins: repositories: - cloud-plugins: + additional-plugins: url: http://localhost:3010/botkube.yaml ``` diff --git a/cmd/executor/ai/main.go b/cmd/executor/ai/main.go index 8f7ede00..ba44fd4e 100644 --- a/cmd/executor/ai/main.go +++ b/cmd/executor/ai/main.go @@ -11,8 +11,7 @@ import ( "strings" "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - aibrain "github.com/kubeshop/botkube-cloud-plugins/internal/source/ai-brain" + aibrain "github.com/kubeshop/botkube-plugins/internal/source/ai-brain" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/executor" "github.com/kubeshop/botkube/pkg/config" @@ -131,9 +130,9 @@ func (*AIFace) Help(context.Context) (api.Message, error) { func main() { executor.Serve(map[string]plugin.Plugin{ pluginName: &executor.Plugin{ - Executor: auth.NewProtectedExecutor(&AIFace{ + Executor: &AIFace{ httpClient: httpx.NewHTTPClient(), - }), + }, }, }) } diff --git a/cmd/executor/exec/main.go b/cmd/executor/exec/main.go index 129c5148..89bfe82e 100644 --- a/cmd/executor/exec/main.go +++ b/cmd/executor/exec/main.go @@ -5,17 +5,15 @@ import ( "fmt" "strings" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/MakeNowJust/heredoc" "github.com/alexflint/go-arg" "github.com/hashicorp/go-plugin" "github.com/sirupsen/logrus" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/getter" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/output" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x" + "github.com/kubeshop/botkube-plugins/internal/executor/x/getter" + "github.com/kubeshop/botkube-plugins/internal/executor/x/output" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/executor" "github.com/kubeshop/botkube/pkg/formatx" @@ -204,7 +202,7 @@ func (i *XExecutor) getKubeconfig(ctx context.Context, log logrus.FieldLogger, i func main() { executor.Serve(map[string]plugin.Plugin{ pluginName: &executor.Plugin{ - Executor: auth.NewProtectedExecutor(&XExecutor{}), + Executor: &XExecutor{}, }, }) } diff --git a/cmd/executor/flux/main.go b/cmd/executor/flux/main.go index ad617098..5b4ad0d3 100644 --- a/cmd/executor/flux/main.go +++ b/cmd/executor/flux/main.go @@ -7,7 +7,7 @@ import ( "github.com/allegro/bigcache/v3" "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/flux" + "github.com/kubeshop/botkube-plugins/internal/executor/flux" "github.com/kubeshop/botkube/pkg/api/executor" "github.com/kubeshop/botkube/pkg/loggerx" ) diff --git a/cmd/executor/gh/main.go b/cmd/executor/gh/main.go index 1071f8f0..2d4ef715 100644 --- a/cmd/executor/gh/main.go +++ b/cmd/executor/gh/main.go @@ -7,8 +7,6 @@ import ( "fmt" "text/template" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/hashicorp/go-plugin" "github.com/kubeshop/botkube/pkg/api" @@ -163,7 +161,7 @@ var depsDownloadLinks = map[string]api.Dependency{ func main() { executor.Serve(map[string]plugin.Plugin{ pluginName: &executor.Plugin{ - Executor: auth.NewProtectedExecutor(&GHExecutor{}), + Executor: &GHExecutor{}, }, }) } diff --git a/cmd/executor/helm/main.go b/cmd/executor/helm/main.go index 78c0ccf4..748a053e 100644 --- a/cmd/executor/helm/main.go +++ b/cmd/executor/helm/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/helm" + "github.com/kubeshop/botkube-plugins/internal/executor/helm" "github.com/kubeshop/botkube/pkg/api/executor" ) diff --git a/cmd/executor/thread-mate/main.go b/cmd/executor/thread-mate/main.go index a083f55d..b613c508 100644 --- a/cmd/executor/thread-mate/main.go +++ b/cmd/executor/thread-mate/main.go @@ -11,7 +11,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" - thmate "github.com/kubeshop/botkube-cloud-plugins/internal/executor/thread-mate" + thmate "github.com/kubeshop/botkube-plugins/internal/executor/thread-mate" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/executor" pluginx "github.com/kubeshop/botkube/pkg/plugin" diff --git a/cmd/source/ai-brain/main.go b/cmd/source/ai-brain/main.go index 84c8fc5e..4db998ca 100644 --- a/cmd/source/ai-brain/main.go +++ b/cmd/source/ai-brain/main.go @@ -3,11 +3,9 @@ package main import ( _ "embed" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/hashicorp/go-plugin" - aibrain "github.com/kubeshop/botkube-cloud-plugins/internal/source/ai-brain" + aibrain "github.com/kubeshop/botkube-plugins/internal/source/ai-brain" "github.com/kubeshop/botkube/pkg/api/source" ) @@ -17,7 +15,7 @@ var version = "dev" func main() { source.Serve(map[string]plugin.Plugin{ aibrain.PluginName: &source.Plugin{ - Source: auth.NewProtectedSource(aibrain.NewSource(version)), + Source: aibrain.NewSource(version), }, }) } diff --git a/cmd/source/argocd/main.go b/cmd/source/argocd/main.go index 5e95f7e2..8a18e2a0 100644 --- a/cmd/source/argocd/main.go +++ b/cmd/source/argocd/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/argocd" + "github.com/kubeshop/botkube-plugins/internal/source/argocd" "github.com/kubeshop/botkube/pkg/api/source" ) diff --git a/cmd/source/github-events/main.go b/cmd/source/github-events/main.go index 10945e5a..4912fe9e 100644 --- a/cmd/source/github-events/main.go +++ b/cmd/source/github-events/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events" + "github.com/kubeshop/botkube-plugins/internal/source/github_events" "github.com/kubeshop/botkube/pkg/api/source" ) diff --git a/cmd/source/keptn/main.go b/cmd/source/keptn/main.go index d06ccd4e..26df2400 100644 --- a/cmd/source/keptn/main.go +++ b/cmd/source/keptn/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/keptn" + "github.com/kubeshop/botkube-plugins/internal/source/keptn" "github.com/kubeshop/botkube/pkg/api/source" ) diff --git a/cmd/source/prometheus/main.go b/cmd/source/prometheus/main.go index 1efd1532..8b9e87f9 100644 --- a/cmd/source/prometheus/main.go +++ b/cmd/source/prometheus/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/go-plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/prometheus" + "github.com/kubeshop/botkube-plugins/internal/source/prometheus" "github.com/kubeshop/botkube/pkg/api/source" ) diff --git a/go.mod b/go.mod index 2ca6d1af..30eba029 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/kubeshop/botkube-cloud-plugins +module github.com/kubeshop/botkube-plugins go 1.21 @@ -20,7 +20,6 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/hashicorp/go-getter v1.7.3 github.com/hashicorp/go-plugin v1.4.10 - github.com/hasura/go-graphql-client v0.8.1 github.com/honeycombio/honeycomb-opentelemetry-go v0.9.0 github.com/honeycombio/otel-config-go v1.13.1 github.com/huandu/xstrings v1.4.0 @@ -34,7 +33,6 @@ require ( github.com/slack-go/slack v0.12.2 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 @@ -112,6 +110,7 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hasura/go-graphql-client v0.8.1 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -170,6 +169,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/host v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect go.opentelemetry.io/contrib/propagators/b3 v1.21.1 // indirect go.opentelemetry.io/contrib/propagators/ot v1.21.1 // indirect diff --git a/hack/assistant-setup/README.md b/hack/assistant-setup/README.md index d33f931d..dc4a6e97 100644 --- a/hack/assistant-setup/README.md +++ b/hack/assistant-setup/README.md @@ -24,12 +24,14 @@ npm install ```sh export OPENAI_API_KEY=... # your Open AI API key -export ASSISTANT_ENV=dev # dev or prod +export OPENAI_ASSISTANT_ID=... # your Open AI Assistant ID + +export OPENAI_PROJECT_ID=... # Optional: your Open AI Project ID +export OPENAI_ORG_ID=... # Optional: your Open AI Organization ID + npm run start ``` -To use your own assistant, modify the `devConfig` in the `index.ts` file. - ## Development ### Refetch content for file search diff --git a/hack/assistant-setup/index.ts b/hack/assistant-setup/index.ts index 94d8786d..db58c8e3 100644 --- a/hack/assistant-setup/index.ts +++ b/hack/assistant-setup/index.ts @@ -4,21 +4,10 @@ import { setupTools } from "./tools"; import dedent from "dedent"; type Config = { - projectID: string; assistantID: string; + apiKey: string; }; -const devConfig = { - projectID: "proj_TyOoSEIuWk2xZEFYWr0EYb2m", - assistantID: "asst_apOFPPk94dLSD5kd0ZbS5ZSR", -}; - -const prodConfig = { - projectID: "proj_eMSaZmIdTYamRviubosCTGKt", - assistantID: "asst_CKNlRasSOUW7PsM0MCnTM88Z", -}; - -const orgID = "org-Tmr8Y7f3doO5hvZGNUtCK1bX"; const model = "gpt-4o"; const temperature = 0.1; @@ -47,34 +36,25 @@ const instructions = dedent` async function main() { let cfg: Config = { - projectID: undefined, - assistantID: "", + assistantID: process.env["OPENAI_ASSISTANT_ID"], + apiKey: process.env["OPENAI_API_KEY"], }; - const assistantEnv = process.env["ASSISTANT_ENV"]; - if (!assistantEnv) { + if (!cfg.assistantID || !cfg.apiKey) { throw new Error( - `Missing ASSISTANT_ENV environment variable; use 'dev' or 'prod'`, + `Missing configuration. Set OPENAI_API_KEY and OPENAI_ASSISTANT_ID environment variables.`, ); } - switch (assistantEnv) { - case "dev": - cfg = devConfig; - break; - case "prod": - cfg = prodConfig; - break; - default: - throw new Error( - `Unknown ASSISTANT_ENV '${assistantEnv}'; use 'dev' or 'prod'`, - ); - } - console.log(`Using ${assistantEnv} assistant`); + const orgID = process.env["OPENAI_ORG_ID"]; + const projID = process.env["OPENAI_PROJECT_ID"]; + const client = new OpenAI({ - apiKey: process.env["OPENAI_API_KEY"], organization: orgID, - project: cfg.projectID, + project: projID, }); + console.log( + `Using org ID ${orgID}, project ID ${projID} and assistant ID ${cfg.assistantID}...`, + ); console.log(`Getting assistant data for ID ${cfg.assistantID}...`); const assistant = await client.beta.assistants.retrieve(cfg.assistantID); diff --git a/internal/auth/executor.go b/internal/auth/executor.go deleted file mode 100644 index eb30cd74..00000000 --- a/internal/auth/executor.go +++ /dev/null @@ -1,73 +0,0 @@ -package auth - -import ( - "context" - - "github.com/kubeshop/botkube-cloud-plugins/internal/remote" - - "github.com/kubeshop/botkube/pkg/api" - "github.com/kubeshop/botkube/pkg/api/executor" -) - -// ProtectedExecutor protects Executor usage without active Cloud connection. -type ProtectedExecutor struct { - underlying executor.Executor - openSourceBlockage bool - initConnectionBlockage bool - authChecker *remote.ConnChecker -} - -// NewProtectedExecutor returns wrapped Executor instance with Cloud connection checker. -func NewProtectedExecutor(exec executor.Executor) executor.Executor { - cfg, ok := remote.GetConfig() - if !ok { - return &ProtectedExecutor{ - underlying: exec, - openSourceBlockage: true, - } - } - - authChecker := remote.NewConnChecker(cfg) - err := authChecker.IsConnectedWithCould() - if err != nil { - return &ProtectedExecutor{ - underlying: exec, - initConnectionBlockage: true, - } - } - noop := func() {} - authChecker.AsyncSupervisor(noop) - - return &ProtectedExecutor{ - underlying: exec, - authChecker: authChecker, - } -} - -// Execute provides executor functionality only with active Cloud connection. -func (e *ProtectedExecutor) Execute(ctx context.Context, input executor.ExecuteInput) (executor.ExecuteOutput, error) { - if e.isBlocked() { - return executor.ExecuteOutput{ - Message: unauthorizedMessage(e.openSourceBlockage), - }, nil - } - - return e.underlying.Execute(ctx, input) -} - -// Metadata returns plugin metadata even without active Cloud connection. -func (e *ProtectedExecutor) Metadata(ctx context.Context) (api.MetadataOutput, error) { - return e.underlying.Metadata(ctx) -} - -// Help provides help functionality only with active Cloud connection. -func (e *ProtectedExecutor) Help(ctx context.Context) (api.Message, error) { - if e.isBlocked() { - return unauthorizedMessage(e.openSourceBlockage), nil - } - return e.underlying.Help(ctx) -} - -func (e *ProtectedExecutor) isBlocked() bool { - return e.openSourceBlockage || e.initConnectionBlockage || e.authChecker.ReachedPermanentBlockage() -} diff --git a/internal/auth/msg.go b/internal/auth/msg.go deleted file mode 100644 index ab2b11fd..00000000 --- a/internal/auth/msg.go +++ /dev/null @@ -1,39 +0,0 @@ -package auth - -import "github.com/kubeshop/botkube/pkg/api" - -func unauthorizedMessage(isOpenSourceInstance bool) api.Message { - btnBuilder := api.NewMessageButtonBuilder() - - if isOpenSourceInstance { - return api.Message{ - Sections: []api.Section{ - { - Base: api.Base{ - Header: "Cloud-Only Plugin Enabled", - Description: "This plugin is available only on Botkube Cloud. To use it, migrate your Botkube Agent instance to Botkube Cloud.", - }, - Buttons: []api.Button{ - btnBuilder.ForURL("Migrate Installation", "https://docs.botkube.io/cli/migrating-installation-to-botkube-cloud", api.ButtonStylePrimary), - btnBuilder.ForURL("Open Botkube Cloud", "https://app.botkube.io"), - }, - }, - }, - } - } - - return api.Message{ - Sections: []api.Section{ - { - Base: api.Base{ - Header: "Lost Connection with Botkube Cloud", - Description: "This plugin requires an active connection to Botkube Cloud. Ensure that your Agent has access to Botkube Cloud with valid credentials. Check Agent logs for more information.", - }, - Buttons: []api.Button{ - btnBuilder.ForURL("See Troubleshooting Guide", "https://docs.botkube.io/operation/common-problems"), - btnBuilder.ForURL("How to Get Agent Logs", "https://docs.botkube.io/operation/diagnostics#agent-logs"), - }, - }, - }, - } -} diff --git a/internal/auth/source.go b/internal/auth/source.go deleted file mode 100644 index 0be01fd8..00000000 --- a/internal/auth/source.go +++ /dev/null @@ -1,87 +0,0 @@ -package auth - -import ( - "context" - "log" - - "github.com/kubeshop/botkube-cloud-plugins/internal/remote" - - "github.com/kubeshop/botkube/pkg/api" - "github.com/kubeshop/botkube/pkg/api/source" -) - -// ProtectedSource protects source usage without active cloud connection. -type ProtectedSource struct { - underlying source.Source - openSourceBlockage bool - initConnectionBlockage bool -} - -// NewProtectedSource returns wrapped Source instance with Cloud conn checker. -func NewProtectedSource(source source.Source) source.Source { - cfg, ok := remote.GetConfig() - if !ok { - return &ProtectedSource{ - underlying: source, - openSourceBlockage: true, - } - } - authChecker := remote.NewConnChecker(cfg) - err := authChecker.IsConnectedWithCould() - if err != nil { - return &ProtectedSource{ - underlying: source, - initConnectionBlockage: true, - } - } - - onPermanentBlockage := func() { - // we may already start multiple streams (`Stream()`), - // so we want to kill the plugin process to stop all of them. - // Once restarted, and we will be still blocked, a proper message will be sent to all configured channels. - log.Fatal("Failed to connect with Cloud") - } - authChecker.AsyncSupervisor(onPermanentBlockage) - return &ProtectedSource{ - underlying: source, - } -} - -// Stream provides stream functionality only with active cloud connection. -func (s *ProtectedSource) Stream(ctx context.Context, in source.StreamInput) (source.StreamOutput, error) { - if s.isBlocked() { - out := source.StreamOutput{ - Event: make(chan source.Event, 1), - } - out.Event <- source.Event{ - Message: unauthorizedMessage(s.openSourceBlockage), - } - - return out, nil - } - - return s.underlying.Stream(ctx, in) -} - -// Metadata returns plugin metadata even without active cloud connection. -func (s *ProtectedSource) Metadata(ctx context.Context) (api.MetadataOutput, error) { - return s.underlying.Metadata(ctx) -} - -// HandleExternalRequest provides external request functionality only with active cloud connection. -func (s *ProtectedSource) HandleExternalRequest(ctx context.Context, in source.ExternalRequestInput) (source.ExternalRequestOutput, error) { - if s.isBlocked() { - out := source.ExternalRequestOutput{ - Event: source.Event{ - Message: unauthorizedMessage(s.openSourceBlockage), - }, - } - return out, nil - } - - return s.underlying.HandleExternalRequest(ctx, in) -} - -func (s *ProtectedSource) isBlocked() bool { - return s.openSourceBlockage || s.initConnectionBlockage -} diff --git a/internal/config/doc.go b/internal/config/doc.go new file mode 100644 index 00000000..f0a10479 --- /dev/null +++ b/internal/config/doc.go @@ -0,0 +1,3 @@ +// Package config contains copy of some files from https://github.com/kubeshop/botkube/tree/efd7cd55ea37c1d01ec9bbc4a014f0960e663925/internal/config. +// At later point we might move the package out from internal directory on Botkube repo and then use it directly. +package config diff --git a/internal/config/env_provider.go b/internal/config/env_provider.go new file mode 100644 index 00000000..33644aeb --- /dev/null +++ b/internal/config/env_provider.go @@ -0,0 +1,31 @@ +package config + +import ( + "context" + "os" + "strings" + + "github.com/kubeshop/botkube/pkg/config" +) + +const ( + // EnvProviderConfigPathsEnvKey holds config paths separated by comma. + EnvProviderConfigPathsEnvKey = "BOTKUBE_CONFIG_PATHS" +) + +// EnvProvider environment config source provider +type EnvProvider struct { +} + +// NewEnvProvider initializes new environment config source provider +func NewEnvProvider() *EnvProvider { + return &EnvProvider{} +} + +// Configs returns list of config file locations +func (e *EnvProvider) Configs(ctx context.Context) (config.YAMLFiles, int, error) { + envCfgs := os.Getenv(EnvProviderConfigPathsEnvKey) + configPaths := strings.Split(envCfgs, ",") + + return NewFileSystemProvider(configPaths).Configs(ctx) +} diff --git a/internal/config/env_provider_test.go b/internal/config/env_provider_test.go new file mode 100644 index 00000000..3787846d --- /dev/null +++ b/internal/config/env_provider_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEnvProviderSuccess(t *testing.T) { + //given + t.Setenv("BOTKUBE_CONFIG_PATHS", "testdata/TestEnvProviderSuccess/config.yaml") + + // when + p := NewEnvProvider() + configs, cfgVer, err := p.Configs(context.Background()) + + // then + require.NoError(t, err) + content, err := os.ReadFile("testdata/TestEnvProviderSuccess/config.yaml") + assert.NoError(t, err) + assert.Equal(t, content, configs[0]) + assert.Equal(t, cfgVer, 0) +} + +func TestEnvProviderErr(t *testing.T) { + // when + p := NewEnvProvider() + _, _, err := p.Configs(context.Background()) + + // then + assert.Equal(t, "while reading a file: read .: is a directory", err.Error()) +} diff --git a/internal/config/fs_provider.go b/internal/config/fs_provider.go new file mode 100644 index 00000000..73b92901 --- /dev/null +++ b/internal/config/fs_provider.go @@ -0,0 +1,57 @@ +package config + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/kubeshop/botkube/pkg/config" +) + +const specialConfigFileNamePrefix = "_" + +// FileSystemProvider allows consumer to pass config files statically +type FileSystemProvider struct { + Files []string +} + +// NewFileSystemProvider initializes new static config source provider +func NewFileSystemProvider(configs []string) *FileSystemProvider { + return &FileSystemProvider{Files: configs} +} + +// Configs returns list of config file locations. +func (e *FileSystemProvider) Configs(_ context.Context) (config.YAMLFiles, int, error) { + configPaths := sortCfgFiles(e.Files) + + var out config.YAMLFiles + for _, path := range configPaths { + raw, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, 0, fmt.Errorf("while reading a file: %w", err) + } + out = append(out, raw) + } + + return out, 0, nil +} + +// sortCfgFiles sorts the config files so that the files that has specialConfigFileNamePrefix are moved to the end of the slice. +func sortCfgFiles(paths []string) []string { + var ordinaryCfgFiles []string + var specialCfgFiles []string + for _, path := range paths { + _, filename := filepath.Split(path) + + if strings.HasPrefix(filename, specialConfigFileNamePrefix) { + specialCfgFiles = append(specialCfgFiles, path) + continue + } + + ordinaryCfgFiles = append(ordinaryCfgFiles, path) + } + + return append(ordinaryCfgFiles, specialCfgFiles...) +} diff --git a/internal/config/fs_provider_test.go b/internal/config/fs_provider_test.go new file mode 100644 index 00000000..51c47663 --- /dev/null +++ b/internal/config/fs_provider_test.go @@ -0,0 +1,47 @@ +package config + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStaticProviderSuccess(t *testing.T) { + // when + p := NewFileSystemProvider([]string{"testdata/TestStaticProviderSuccess/config.yaml"}) + configs, cfgVer, err := p.Configs(context.Background()) + + // then + require.NoError(t, err) + content, err := os.ReadFile("testdata/TestStaticProviderSuccess/config.yaml") + assert.NoError(t, err) + assert.Equal(t, content, configs[0]) + assert.Equal(t, cfgVer, 0) +} + +func TestSortCfgFiles(t *testing.T) { + tests := map[string]struct { + input []string + expected []string + }{ + "No special files": { + input: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/_bar/baz.yaml"}, + expected: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/_bar/baz.yaml"}, + }, + "Special files": { + input: []string{"_test.yaml", "config.yaml", "_foo.yaml", ".bar.yaml", "/bar/_baz.yaml"}, + expected: []string{"config.yaml", ".bar.yaml", "_test.yaml", "_foo.yaml", "/bar/_baz.yaml"}, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + actual := sortCfgFiles(test.input) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/internal/config/testdata/TestEnvProviderSuccess/config.yaml b/internal/config/testdata/TestEnvProviderSuccess/config.yaml new file mode 100644 index 00000000..bf10b4a8 --- /dev/null +++ b/internal/config/testdata/TestEnvProviderSuccess/config.yaml @@ -0,0 +1,216 @@ +communications: # req 1 elm. + 'default-workspace': + slack: + enabled: false + channels: + 'alias': + name: 'SLACK_CHANNEL' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + token: 'SLACK_API_TOKEN' + notification: + type: short + socketSlack: + enabled: true + channels: + alias: + name: SLACK_CHANNEL + notification: + disabled: false + bindings: + sources: + - k8s-events + executors: + - kubectl-read-only + notification: + type: short + botToken: 'SLACK_BOT_TOKEN' + appToken: 'SLACK_APP_TOKEN' + + mattermost: + enabled: false + url: 'MATTERMOST_SERVER_URL' + token: 'MATTERMOST_TOKEN' + team: 'MATTERMOST_TEAM' + channels: + 'alias': + name: 'MATTERMOST_CHANNEL' + notification: + disabled: true + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + + teams: + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + port: 3978 + + discord: + enabled: false + token: 'DISCORD_TOKEN' + botID: 'DISCORD_BOT_ID' + channels: + 'alias': + id: 'DISCORD_CHANNEL_ID' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + + elasticsearch: + enabled: false + awsSigning: + enabled: false + awsRegion: 'us-east-1' + roleArn: '' + server: 'ELASTICSEARCH_ADDRESS' + username: 'ELASTICSEARCH_USERNAME' + password: 'ELASTICSEARCH_PASSWORD' + skipTLSVerify: false + indices: + 'alias': + name: botkube + type: botkube-event + shards: 1 + bindings: + sources: + - "k8s-events" + + webhook: + enabled: false + url: 'WEBHOOK_URL' + bindings: + sources: + - k8s-events + +sources: + 'k8s-events': + displayName: "Plugins & Builtins" + + kubernetes: + recommendations: + pod: + noLatestImageTag: false + labelsSet: true + ingress: + backendServiceValid: true + tlsSecretValid: false + namespaces: + include: + - ".*" + exclude: [ ] + event: + reason: ".*" + message: "^Error .*" + types: + - create + - delete + - error + annotations: + my-annotation: "true" + labels: + my-label: "true" + + resources: + - type: v1/pods + - type: v1/services + - type: networking.k8s.io/v1/ingresses + - type: v1/nodes + event: + reason: NodeNotReady + message: "status .*" + - type: v1/namespaces + - type: v1/persistentvolumes + - type: v1/persistentvolumeclaims + - type: v1/configmaps + namespaces: # Overrides 'source.kubernetes.namespaces + include: + - "default" + exclude: + - "kube-system" + - type: rbac.authorization.k8s.io/v1/roles + - type: rbac.authorization.k8s.io/v1/rolebindings + - type: rbac.authorization.k8s.io/v1/clusterrolebindings + - type: rbac.authorization.k8s.io/v1/clusterroles + - type: apps/v1/daemonsets + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.numberReady + - type: batch/v1/jobs + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + annotations: # Overrides 'source'.kubernetes.annotations + my-own-annotation: "true" + labels: # Overrides 'source'.kubernetes.labels + my-own-label: "true" + name: "my-.*" + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.conditions[*].type + - type: apps/v1/deployments + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.availableReplicas + - type: apps/v1/statefulsets + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.readyReplicas + + botkube/keptn: + enabled: true + config: + field: value + +filters: + kubernetes: + objectAnnotationChecker: true + nodeEventsChecker: false diff --git a/internal/config/testdata/TestGetProvider/all.yaml b/internal/config/testdata/TestGetProvider/all.yaml new file mode 100644 index 00000000..a467d34e --- /dev/null +++ b/internal/config/testdata/TestGetProvider/all.yaml @@ -0,0 +1,17 @@ +analytics: + disable: true +actions: + show-created-resource: + enabled: true + displayName: Display created resource + command: kubectl describe {{.Event.ResourceType}} -n {{.Event.Namespace}} {{.Event.Name}} + bindings: + sources: + - k8s-events + executors: + - kubectl-read-only +plugins: + cacheDir: /tmp + repositories: + botkube: + url: http://localhost:3000/botkube.yaml diff --git a/internal/config/testdata/TestGetProvider/first.yaml b/internal/config/testdata/TestGetProvider/first.yaml new file mode 100644 index 00000000..4c5033f3 --- /dev/null +++ b/internal/config/testdata/TestGetProvider/first.yaml @@ -0,0 +1,2 @@ +analytics: + disable: true diff --git a/internal/config/testdata/TestGetProvider/from-cli-flag-second.yaml b/internal/config/testdata/TestGetProvider/from-cli-flag-second.yaml new file mode 100644 index 00000000..90925194 --- /dev/null +++ b/internal/config/testdata/TestGetProvider/from-cli-flag-second.yaml @@ -0,0 +1,5 @@ +plugins: + cacheDir: /tmp2 + repositories: + botkube: + url: http://localhost:3000/botkube.yaml diff --git a/internal/config/testdata/TestGetProvider/from-cli-flag.yaml b/internal/config/testdata/TestGetProvider/from-cli-flag.yaml new file mode 100644 index 00000000..fc852a5a --- /dev/null +++ b/internal/config/testdata/TestGetProvider/from-cli-flag.yaml @@ -0,0 +1,2 @@ +analytics: + disable: false diff --git a/internal/config/testdata/TestGetProvider/second.yaml b/internal/config/testdata/TestGetProvider/second.yaml new file mode 100644 index 00000000..c7688b59 --- /dev/null +++ b/internal/config/testdata/TestGetProvider/second.yaml @@ -0,0 +1,10 @@ +actions: + show-created-resource: + enabled: true + displayName: Display created resource + command: kubectl describe {{.Event.ResourceType}} -n {{.Event.Namespace}} {{.Event.Name}} + bindings: + sources: + - k8s-events + executors: + - kubectl-read-only diff --git a/internal/config/testdata/TestGetProvider/third.yaml b/internal/config/testdata/TestGetProvider/third.yaml new file mode 100644 index 00000000..ff926e08 --- /dev/null +++ b/internal/config/testdata/TestGetProvider/third.yaml @@ -0,0 +1,5 @@ +plugins: + cacheDir: /tmp + repositories: + botkube: + url: http://localhost:3000/botkube.yaml diff --git a/internal/config/testdata/TestGqlProviderSuccess/config.yaml b/internal/config/testdata/TestGqlProviderSuccess/config.yaml new file mode 100644 index 00000000..de8196c9 --- /dev/null +++ b/internal/config/testdata/TestGqlProviderSuccess/config.yaml @@ -0,0 +1,58 @@ +communications: + default-group: + socketSlack: + appToken: xapp-1-A047D1ZJ03B-4262138376928 + botToken: xoxb-3933899240838 + channels: + botkube-demo: + bindings: + executors: + - kubectl-read-only + sources: + - kubernetes-info + name: botkube-demo + notification: + disabled: false + enabled: true +executors: + kubectl-read-only: + kubectl: + commands: + resources: + - deployments + - pods + - namespaces + - daemonsets + - statefulsets + - storageclasses + - nodes + verbs: + - api-resources + - api-versions + - cluster-info + - describe + - diff + - explain + - get + - logs + - top + - auth + defaultNamespace: default + enabled: true + namespaces: + include: + - .* + restrictAccess: false +settings: + clusterName: qa +sources: + kubernetes-info: + displayName: Kubernetes Information + kubernetes: + recommendations: + ingress: + backendServiceValid: true + tlsSecretValid: true + pod: + labelsSet: true + noLatestImageTag: true diff --git a/internal/config/testdata/TestStaticProviderSuccess/config.yaml b/internal/config/testdata/TestStaticProviderSuccess/config.yaml new file mode 100644 index 00000000..bf10b4a8 --- /dev/null +++ b/internal/config/testdata/TestStaticProviderSuccess/config.yaml @@ -0,0 +1,216 @@ +communications: # req 1 elm. + 'default-workspace': + slack: + enabled: false + channels: + 'alias': + name: 'SLACK_CHANNEL' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + token: 'SLACK_API_TOKEN' + notification: + type: short + socketSlack: + enabled: true + channels: + alias: + name: SLACK_CHANNEL + notification: + disabled: false + bindings: + sources: + - k8s-events + executors: + - kubectl-read-only + notification: + type: short + botToken: 'SLACK_BOT_TOKEN' + appToken: 'SLACK_APP_TOKEN' + + mattermost: + enabled: false + url: 'MATTERMOST_SERVER_URL' + token: 'MATTERMOST_TOKEN' + team: 'MATTERMOST_TEAM' + channels: + 'alias': + name: 'MATTERMOST_CHANNEL' + notification: + disabled: true + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + + teams: + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + port: 3978 + + discord: + enabled: false + token: 'DISCORD_TOKEN' + botID: 'DISCORD_BOT_ID' + channels: + 'alias': + id: 'DISCORD_CHANNEL_ID' + bindings: + executors: + - kubectl-read-only + sources: + - k8s-events + notification: + type: short + + elasticsearch: + enabled: false + awsSigning: + enabled: false + awsRegion: 'us-east-1' + roleArn: '' + server: 'ELASTICSEARCH_ADDRESS' + username: 'ELASTICSEARCH_USERNAME' + password: 'ELASTICSEARCH_PASSWORD' + skipTLSVerify: false + indices: + 'alias': + name: botkube + type: botkube-event + shards: 1 + bindings: + sources: + - "k8s-events" + + webhook: + enabled: false + url: 'WEBHOOK_URL' + bindings: + sources: + - k8s-events + +sources: + 'k8s-events': + displayName: "Plugins & Builtins" + + kubernetes: + recommendations: + pod: + noLatestImageTag: false + labelsSet: true + ingress: + backendServiceValid: true + tlsSecretValid: false + namespaces: + include: + - ".*" + exclude: [ ] + event: + reason: ".*" + message: "^Error .*" + types: + - create + - delete + - error + annotations: + my-annotation: "true" + labels: + my-label: "true" + + resources: + - type: v1/pods + - type: v1/services + - type: networking.k8s.io/v1/ingresses + - type: v1/nodes + event: + reason: NodeNotReady + message: "status .*" + - type: v1/namespaces + - type: v1/persistentvolumes + - type: v1/persistentvolumeclaims + - type: v1/configmaps + namespaces: # Overrides 'source.kubernetes.namespaces + include: + - "default" + exclude: + - "kube-system" + - type: rbac.authorization.k8s.io/v1/roles + - type: rbac.authorization.k8s.io/v1/rolebindings + - type: rbac.authorization.k8s.io/v1/clusterrolebindings + - type: rbac.authorization.k8s.io/v1/clusterroles + - type: apps/v1/daemonsets + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.numberReady + - type: batch/v1/jobs + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + annotations: # Overrides 'source'.kubernetes.annotations + my-own-annotation: "true" + labels: # Overrides 'source'.kubernetes.labels + my-own-label: "true" + name: "my-.*" + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.conditions[*].type + - type: apps/v1/deployments + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.availableReplicas + - type: apps/v1/statefulsets + event: # Overrides 'source'.kubernetes.event + types: + - create + - update + - delete + - error + updateSetting: + includeDiff: true + fields: + - spec.template.spec.containers[*].image + - status.readyReplicas + + botkube/keptn: + enabled: true + config: + field: value + +filters: + kubernetes: + objectAnnotationChecker: true + nodeEventsChecker: false diff --git a/internal/executor/flux/commands/template.go b/internal/executor/flux/commands/template.go index 596fd71e..54aff74e 100644 --- a/internal/executor/flux/commands/template.go +++ b/internal/executor/flux/commands/template.go @@ -7,7 +7,7 @@ import ( "gopkg.in/yaml.v3" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" ) //go:embed store diff --git a/internal/executor/flux/executor.go b/internal/executor/flux/executor.go index 89064e7b..a7c04755 100644 --- a/internal/executor/flux/executor.go +++ b/internal/executor/flux/executor.go @@ -5,11 +5,10 @@ import ( _ "embed" "fmt" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/flux/commands" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/output" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/flux/commands" + "github.com/kubeshop/botkube-plugins/internal/executor/x" + "github.com/kubeshop/botkube-plugins/internal/executor/x/output" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" "github.com/allegro/bigcache/v3" "github.com/kubeshop/botkube/pkg/api" @@ -42,7 +41,7 @@ func NewExecutor(cache *bigcache.BigCache, ver string) executor.Executor { pluginVersion: ver, cache: cache, } - return auth.NewProtectedExecutor(exec) + return exec } // Metadata returns details about the Flux plugin. diff --git a/internal/executor/helm/executor.go b/internal/executor/helm/executor.go index 2d230b3f..29378a7f 100644 --- a/internal/executor/helm/executor.go +++ b/internal/executor/helm/executor.go @@ -6,8 +6,6 @@ import ( "fmt" "os" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/alexflint/go-arg" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/executor" @@ -61,7 +59,7 @@ func NewExecutor(ver string) executor.Executor { pluginVersion: ver, executeCommand: pluginx.ExecuteCommand, } - return auth.NewProtectedExecutor(exec) + return exec } // Metadata returns details about Helm plugin. diff --git a/internal/executor/thread-mate/service.go b/internal/executor/thread-mate/service.go index 4497eedb..9c4698e7 100644 --- a/internal/executor/thread-mate/service.go +++ b/internal/executor/thread-mate/service.go @@ -67,6 +67,7 @@ func New(cfg Config, cfgDumper *ConfigMapDumper) *ThreadMate { next: uint32(rand.Int31n(int32(len(assignees)))), // randomize the first next person on each start }, }, + //nolint:gosec membersLen: uint32(len(assignees)), lastProcessedActivity: sync.Map{}, cfgDumper: cfgDumper, diff --git a/internal/executor/x/config.go b/internal/executor/x/config.go index 11f39dcd..d78ea94e 100644 --- a/internal/executor/x/config.go +++ b/internal/executor/x/config.go @@ -5,7 +5,7 @@ import ( pluginx "github.com/kubeshop/botkube/pkg/plugin" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/getter" + "github.com/kubeshop/botkube-plugins/internal/executor/x/getter" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/config" ) diff --git a/internal/executor/x/output/message_parser.go b/internal/executor/x/output/message_parser.go index cff54946..3f10e98a 100644 --- a/internal/executor/x/output/message_parser.go +++ b/internal/executor/x/output/message_parser.go @@ -9,9 +9,9 @@ import ( "github.com/huandu/xstrings" "github.com/sirupsen/logrus" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/formatx" ) diff --git a/internal/executor/x/output/message_tutorial.go b/internal/executor/x/output/message_tutorial.go index 9246e30a..897c4594 100644 --- a/internal/executor/x/output/message_tutorial.go +++ b/internal/executor/x/output/message_tutorial.go @@ -3,9 +3,9 @@ package output import ( "fmt" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/mathx" ) diff --git a/internal/executor/x/output/message_wrapper.go b/internal/executor/x/output/message_wrapper.go index 5557e56c..fe36e39b 100644 --- a/internal/executor/x/output/message_wrapper.go +++ b/internal/executor/x/output/message_wrapper.go @@ -1,8 +1,8 @@ package output import ( - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" ) diff --git a/internal/executor/x/renderer.go b/internal/executor/x/renderer.go index 3e729555..91b2a074 100644 --- a/internal/executor/x/renderer.go +++ b/internal/executor/x/renderer.go @@ -9,8 +9,8 @@ import ( "golang.org/x/exp/maps" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" ) diff --git a/internal/executor/x/run.go b/internal/executor/x/run.go index 4b00edaf..1b51ec4a 100644 --- a/internal/executor/x/run.go +++ b/internal/executor/x/run.go @@ -7,9 +7,9 @@ import ( "github.com/gookit/color" "github.com/sirupsen/logrus" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/getter" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x/getter" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/executor" pluginx "github.com/kubeshop/botkube/pkg/plugin" diff --git a/internal/executor/x/run_test.go b/internal/executor/x/run_test.go index 68484a8f..dd41ce87 100644 --- a/internal/executor/x/run_test.go +++ b/internal/executor/x/run_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/getter" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/state" - "github.com/kubeshop/botkube-cloud-plugins/internal/executor/x/template" + "github.com/kubeshop/botkube-plugins/internal/executor/x/getter" + "github.com/kubeshop/botkube-plugins/internal/executor/x/state" + "github.com/kubeshop/botkube-plugins/internal/executor/x/template" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/config" "github.com/kubeshop/botkube/pkg/loggerx" diff --git a/internal/remote/config.go b/internal/remote/config.go deleted file mode 100644 index 046b005f..00000000 --- a/internal/remote/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package remote - -import ( - "os" -) - -const ( - // ProviderEndpointEnvKey holds config provider endpoint. - //nolint:gosec // Potential hardcoded credentials - ProviderEndpointEnvKey = "CONFIG_PROVIDER_ENDPOINT" - // ProviderIdentifierEnvKey holds config provider identifier. - ProviderIdentifierEnvKey = "CONFIG_PROVIDER_IDENTIFIER" - // ProviderAPIKeyEnvKey holds config provider API key. - //nolint:gosec // warns us about 'Potential hardcoded credentials' but there is no security issue here - ProviderAPIKeyEnvKey = "CONFIG_PROVIDER_API_KEY" -) - -// Config holds configuration for remote configuration. -type Config struct { - Endpoint string - Identifier string - APIKey string -} - -// GetConfig returns remote configuration if it is set. -func GetConfig() (Config, bool) { - if os.Getenv(ProviderIdentifierEnvKey) == "" { - return Config{}, false - } - - return Config{ - Endpoint: os.Getenv(ProviderEndpointEnvKey), - Identifier: os.Getenv(ProviderIdentifierEnvKey), - APIKey: os.Getenv(ProviderAPIKeyEnvKey), - }, true -} diff --git a/internal/remote/conn_checker.go b/internal/remote/conn_checker.go deleted file mode 100644 index 55ad4800..00000000 --- a/internal/remote/conn_checker.go +++ /dev/null @@ -1,72 +0,0 @@ -package remote - -import ( - "math/rand" - "sync/atomic" - "time" - - "github.com/kubeshop/botkube/pkg/config" - "github.com/kubeshop/botkube/pkg/loggerx" - "github.com/sirupsen/logrus" -) - -const ( - maxFailedAttempts = 3 -) - -// ConnChecker checks if Cloud connection is active. -type ConnChecker struct { - failedAttempts atomic.Uint32 - deployClient *DeploymentClient - log *logrus.Entry -} - -// NewConnChecker returns new connection checker. -func NewConnChecker(cfg Config) *ConnChecker { - return &ConnChecker{ - deployClient: NewDeploymentClient(cfg), - log: loggerx.New(config.Logger{ - Level: "info", - }).WithField("service", "auth-checker"), - } -} - -// IsConnectedWithCould returns error if Cloud connection is not active. -func (a *ConnChecker) IsConnectedWithCould() error { - return a.deployClient.IsConnectedWithCould() -} - -// AsyncSupervisor starts connection checker. -func (a *ConnChecker) AsyncSupervisor(onPermanentBlockage func()) { - go func() { - timer := time.NewTimer(a.nextCheck()) - defer timer.Stop() - - for { - timer.Reset(a.nextCheck()) - <-timer.C - err := a.deployClient.IsConnectedWithCould() - if err != nil { - a.failedAttempts.Add(1) - a.log.WithField("attempt", a.failedAttempts.Load()).Error("Licence check failed") - if a.ReachedPermanentBlockage() { - onPermanentBlockage() - return - } - continue - } - a.failedAttempts.Store(0) - } - }() -} - -// ReachedPermanentBlockage returns true if reached max failed attempts. -func (a *ConnChecker) ReachedPermanentBlockage() bool { - return a.failedAttempts.Load() > maxFailedAttempts -} - -func (a *ConnChecker) nextCheck() time.Duration { - //nolint: gosec - i := rand.Intn(3) + 1 // [1,3] - return time.Duration(i) * time.Hour -} diff --git a/internal/remote/deploy_client.go b/internal/remote/deploy_client.go deleted file mode 100644 index f641c239..00000000 --- a/internal/remote/deploy_client.go +++ /dev/null @@ -1,84 +0,0 @@ -package remote - -import ( - "context" - "fmt" - - "github.com/avast/retry-go/v4" - "github.com/hasura/go-graphql-client" -) - -const maxRetries = 5 - -// GraphQLClient defines GraphQL client. -type GraphQLClient interface { - Client() *graphql.Client - DeploymentID() string -} - -// DeploymentClient defines GraphQL client for Deployment. -type DeploymentClient struct { - client GraphQLClient -} - -// NewDeploymentClient initializes GraphQL client. -func NewDeploymentClient(cfg Config) *DeploymentClient { - return &DeploymentClient{client: NewDefaultGqlClient(cfg)} -} - -// IsConnectedWithCould returns whether connected to Botkube Cloud -func (d *DeploymentClient) IsConnectedWithCould() error { - var query struct { - Deployment struct { - ID string - } `graphql:"deployment(id: $id)"` - } - deployID := d.client.DeploymentID() - variables := map[string]interface{}{ - "id": graphql.ID(deployID), - } - err := d.withRetries(func() error { - return d.client.Client().Query(context.Background(), &query, variables) - }) - - if err != nil { - return err - } - - if query.Deployment.ID != deployID { - return fmt.Errorf("instance with id %q is not recognized by Cloud", deployID) - } - - return nil -} - -// GetConfigOutput returns deployment with Botkube configuration. -type GetConfigOutput struct { - ResourceVersion int - YAMLConfig string -} - -// GetConfig retrieves deployment configuration. -func (d *DeploymentClient) GetConfig(ctx context.Context) (GetConfigOutput, error) { - var query struct { - Deployment GetConfigOutput `graphql:"deployment(id: $id)"` - } - deployID := d.client.DeploymentID() - variables := map[string]interface{}{ - "id": graphql.ID(deployID), - } - err := d.client.Client().Query(ctx, &query, variables) - if err != nil { - return GetConfigOutput{}, fmt.Errorf("while getting config with resource version for %q: %w", deployID, err) - } - return query.Deployment, nil -} - -func (d *DeploymentClient) withRetries(fn func() error) error { - return retry.Do( - func() error { - return fn() - }, - retry.Attempts(maxRetries), - ) -} diff --git a/internal/remote/gql_client.go b/internal/remote/gql_client.go deleted file mode 100644 index de5590d9..00000000 --- a/internal/remote/gql_client.go +++ /dev/null @@ -1,63 +0,0 @@ -package remote - -import ( - "net/http" - "time" - - "github.com/hasura/go-graphql-client" -) - -const ( - defaultTimeout = 30 * time.Second - //nolint:gosec // warns us about 'Potential hardcoded credentials' but there is no security issue here - apiKeyHeaderName = "X-API-Key" -) - -// Gql defines GraphQL client data structure. -type Gql struct { - client *graphql.Client - deployID string -} - -// NewDefaultGqlClient initializes GraphQL client with default options. -func NewDefaultGqlClient(remoteCfg Config) *Gql { - httpCli := &http.Client{ - Transport: newAPIKeySecuredTransport(remoteCfg.APIKey), - Timeout: defaultTimeout, - } - - return &Gql{ - client: graphql.NewClient(remoteCfg.Endpoint, httpCli), - deployID: remoteCfg.Identifier, - } -} - -// DeploymentID returns deployment ID. -func (g *Gql) DeploymentID() string { - return g.deployID -} - -// Client returns GraphQL client. -func (g *Gql) Client() *graphql.Client { - return g.client -} - -type apiKeySecuredTransport struct { - apiKey string - transport *http.Transport -} - -func newAPIKeySecuredTransport(apiKey string) *apiKeySecuredTransport { - return &apiKeySecuredTransport{ - apiKey: apiKey, - transport: http.DefaultTransport.(*http.Transport).Clone(), - } -} - -// RoundTrip adds API key to request header and executes RoundTrip for the underlying transport. -func (t *apiKeySecuredTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.apiKey != "" { - req.Header.Set(apiKeyHeaderName, t.apiKey) - } - return t.transport.RoundTrip(req) -} diff --git a/internal/source/ai-brain/assistant.go b/internal/source/ai-brain/assistant.go index f609a84c..bce4f791 100644 --- a/internal/source/ai-brain/assistant.go +++ b/internal/source/ai-brain/assistant.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "net/http" - "os" "strings" "time" @@ -14,9 +12,7 @@ import ( "github.com/kubeshop/botkube/pkg/ptr" - "github.com/kubeshop/botkube-cloud-plugins/internal/otelx" - "github.com/kubeshop/botkube-cloud-plugins/internal/remote" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "github.com/kubeshop/botkube-plugins/internal/otelx" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -123,10 +119,7 @@ func newAssistant(cfg *Config, log logrus.FieldLogger, out chan source.Event, ku } config := openai.DefaultConfig("") - config.HTTPClient = &http.Client{ - Transport: newAPIKeySecuredTransport(), - } - config.BaseURL = cfg.OpenAICloudServiceURL + config.BaseURL = cfg.OpenAIBaseURL config.AssistantVersion = "v2" return &assistant{ @@ -297,8 +290,7 @@ func (i *assistant) createNewThread(ctx context.Context, p *Payload) (openai.Thr threadReq := openai.ThreadRequest{ Metadata: map[string]any{ - "messageId": p.MessageID, - "instanceId": os.Getenv(remote.ProviderIdentifierEnvKey), + "messageId": p.MessageID, }, Messages: []openai.ThreadMessage{ { @@ -377,10 +369,9 @@ func (i *assistant) handleStatusCompleted(ctx context.Context, run openai.Run, p msgs := strings.Split(textValue, msgSplitPattern) isMultiMessage := len(msgs) > 1 - for j, msg := range msgs { - isLastMessage := j == len(msgs)-1 + for _, msg := range msgs { i.out <- source.Event{ - Message: msgAIAnswer(run, p, msg, toolCalls, isLastMessage), + Message: msgAIAnswer(p, msg, toolCalls), } if isMultiMessage { @@ -503,22 +494,3 @@ func (i *assistant) listToolCalls(ctx context.Context, threadID, runID string) ( return getFriendlyToolCallsFromRunSteps(runSteps), nil } - -type apiKeySecuredTransport struct { - transport http.RoundTripper -} - -func newAPIKeySecuredTransport() *apiKeySecuredTransport { - return &apiKeySecuredTransport{ - transport: otelhttp.NewTransport(http.DefaultTransport), - } -} - -func (t *apiKeySecuredTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set("X-API-Key", os.Getenv(remote.ProviderAPIKeyEnvKey)) - return t.transport.RoundTrip(req) -} - -func instanceID() string { - return os.Getenv(remote.ProviderIdentifierEnvKey) -} diff --git a/internal/source/ai-brain/botkube_tools.go b/internal/source/ai-brain/botkube_tools.go index 917d7748..1714f72e 100644 --- a/internal/source/ai-brain/botkube_tools.go +++ b/internal/source/ai-brain/botkube_tools.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - "github.com/kubeshop/botkube-cloud-plugins/internal/remote" + cfginternal "github.com/kubeshop/botkube-plugins/internal/config" "github.com/kubeshop/botkube/pkg/config" "github.com/kubeshop/botkube/pkg/httpx" "github.com/kubeshop/botkube/pkg/ptr" @@ -22,7 +22,6 @@ const ( // BotkubeRunner is a runner that executes Botkube related commands. type BotkubeRunner struct { tracer trace.Tracer - deployCli *remote.DeploymentClient httpCli *http.Client rawStartupConfig string startupConfig *ConfigWithDetails @@ -37,15 +36,9 @@ type ConfigWithDetails struct { // NewBotkubeRunner creates new runner instance. func NewBotkubeRunner(tracer trace.Tracer) (*BotkubeRunner, error) { - cfg, ok := remote.GetConfig() - if !ok { - return nil, fmt.Errorf("cannot get Botkube cloud related environment variables") - } - r := &BotkubeRunner{ - tracer: tracer, - deployCli: remote.NewDeploymentClient(cfg), - httpCli: httpx.NewHTTPClient(), + tracer: tracer, + httpCli: httpx.NewHTTPClient(), } err := r.initStartupConfig() @@ -116,15 +109,12 @@ func (r *BotkubeRunner) initStartupConfig() error { return nil } func (r *BotkubeRunner) fetchConfig(ctx context.Context) (string, *ConfigWithDetails, error) { - out, err := r.deployCli.GetConfig(ctx) + files, _, err := cfginternal.NewEnvProvider().Configs(ctx) if err != nil { - return "", nil, fmt.Errorf("while getting Botkube configuration: %w", err) + return "", nil, fmt.Errorf("while loading app configuration: %w", err) } - // Load with defaults to make sure Agent's ENVs are also taken into account - cfg, details, err := config.LoadWithDefaults([][]byte{ - []byte(out.YAMLConfig), - }) + cfg, details, err := config.LoadWithDefaults(files) if err != nil { return "", nil, fmt.Errorf("while merging app configuration: %w", err) } diff --git a/internal/source/ai-brain/config.go b/internal/source/ai-brain/config.go index 2a00202f..883a7df8 100644 --- a/internal/source/ai-brain/config.go +++ b/internal/source/ai-brain/config.go @@ -16,7 +16,7 @@ const assistantID = "asst_eMM9QaWLi6cajHE4PdG1yU53" // Config holds source configuration. type Config struct { Log config.Logger `yaml:"log"` - OpenAICloudServiceURL string `yaml:"openAICloudServiceURL"` + OpenAIBaseURL string `yaml:"openAIBaseURL"` OpenAIAssistantID string `yaml:"openAIAssistantId"` HoneycombAPIKey string `yaml:"honeycombAPIKey"` HoneycombSampleRate int `yaml:"honeycombSampleRate"` @@ -30,9 +30,6 @@ func (c *Config) Validate() error { if c.OpenAIAssistantID == "" { issues = multierror.Append(issues, errors.New("the Open AI Assistant ID cannot be empty")) } - if c.OpenAICloudServiceURL == "" { - issues = multierror.Append(issues, errors.New("the Open AI Cloud Service URL cannot be empty")) - } return issues.ErrorOrNil() } diff --git a/internal/source/ai-brain/config_schema.json b/internal/source/ai-brain/config_schema.json index 324e660f..3d77e1f6 100644 --- a/internal/source/ai-brain/config_schema.json +++ b/internal/source/ai-brain/config_schema.json @@ -4,8 +4,8 @@ "description": "Calls AI engine with incoming webhook prompts and streams the response.", "type": "object", "properties": { - "openAICloudServiceURL": { - "title": "OpenAI Cloud Service URL", + "openAIBaseURL": { + "title": "OpenAI Service URL", "type": "string" }, "openAIAssistantId": { @@ -79,6 +79,6 @@ } }, "required": [ - "openAICloudServiceURL" + "openAIBaseURL" ] } diff --git a/internal/source/ai-brain/response.go b/internal/source/ai-brain/response.go index bc9e05d9..861e902c 100644 --- a/internal/source/ai-brain/response.go +++ b/internal/source/ai-brain/response.go @@ -8,14 +8,12 @@ import ( "time" "github.com/kubeshop/botkube/pkg/api" - "github.com/sashabaranov/go-openai" ) const ( - teamsMessageIDSubstr = "thread.tacv2" - reportResponseBtnName = "🚩Report response" - maxPromptLen = 500 - aiContentWarning = "AI-generated content may be incorrect." + teamsMessageIDSubstr = "thread.tacv2" + maxPromptLen = 500 + aiContentWarning = "AI-generated content may be incorrect." ) var ( @@ -108,10 +106,9 @@ func msgNoAIAnswer(messageID string) api.Message { } } -func msgAIAnswer(run openai.Run, payload *Payload, response string, toolCalls map[string]struct{}, isLastMessage bool) api.Message { +func msgAIAnswer(payload *Payload, response string, toolCalls map[string]struct{}) api.Message { var ( msgID = payload.MessageID - btnBldr = api.NewMessageButtonBuilder() usedToolsMsg = printUsedTools(toolCalls) ) @@ -129,17 +126,6 @@ func msgAIAnswer(run openai.Run, payload *Payload, response string, toolCalls ma }, } - if isLastMessage { - // add Report button - teamsRes.Sections = []api.Section{ - { - Buttons: api.Buttons{ - btnBldr.ForCommandWithoutDesc(reportResponseBtnName, reportCmd(run, payload)), - }, - }, - } - } - return teamsRes } @@ -164,8 +150,10 @@ func msgAIAnswer(run openai.Run, payload *Payload, response string, toolCalls ma Style: api.SectionStyle{ Divider: api.DividerStyleTopNone, }, - Buttons: api.Buttons{ - btnBldr.ForCommandWithItalicDesc(reportResponseBtnName, aiContentWarning, reportCmd(run, payload)), + Context: []api.ContextItem{ + { + Text: fmt.Sprintf("_%s_", aiContentWarning), + }, }, }, }, @@ -214,17 +202,6 @@ func markdownToTeams(text, additionalFooterMessage string) string { return text } -func reportCmd(run openai.Run, payload *Payload) string { - cmd := strings.Builder{} - cmd.WriteString(`cloud report analytics --bk-cmd-header="Report invalid AI response" -t=ai-invalid-response `) - cmd.WriteString(fmt.Sprintf("-f=MESSAGE_ID=%q ", payload.MessageID)) - cmd.WriteString(fmt.Sprintf("-f=INSTANCE_ID=%q ", instanceID())) - cmd.WriteString(fmt.Sprintf("-f=RUN_ID=%q ", run.ID)) - cmd.WriteString(fmt.Sprintf("-f=THREAD_ID=%q ", run.ThreadID)) - cmd.WriteString(fmt.Sprintf("-f=PROMPT=%q", ellipticalTruncate(payload.Prompt, maxPromptLen))) - - return cmd.String() -} func ellipticalTruncate(s string, max int) string { if len(s) <= max { return s diff --git a/internal/source/ai-brain/response_test.go b/internal/source/ai-brain/response_test.go index 93329a60..cd79dce0 100644 --- a/internal/source/ai-brain/response_test.go +++ b/internal/source/ai-brain/response_test.go @@ -23,17 +23,17 @@ func TestConvertProperlyAIAnswer(t *testing.T) { require.NoError(t, err) // Slack - out := msgAIAnswer(openai.Run{}, &Payload{ + out := msgAIAnswer(&Payload{ MessageID: "42.42", Prompt: "This is a test", - }, string(md), nil, true) + }, string(md), nil) assertGolden(t, out.Sections[0].Base.Body.Plaintext, "slack.golden.md") // Teams - out = msgAIAnswer(openai.Run{}, &Payload{ + out = msgAIAnswer(&Payload{ MessageID: "19:d25cbf7cbfa74d22b42a2918452e1153@thread.tacv2", Prompt: "This is a test", - }, string(md), nil, true) + }, string(md), nil) assertGolden(t, out.BaseBody.Plaintext, "teams.golden.md") } @@ -97,37 +97,37 @@ func TestConvertProperlyAIAnswerWithTools(t *testing.T) { convertedToolCalls := getFriendlyToolCallsFromRunSteps(toolCalls) // Slack - out := msgAIAnswer(openai.Run{}, &Payload{ + out := msgAIAnswer(&Payload{ MessageID: "42.42", Prompt: "This is a test", - }, string(md), convertedToolCalls, true) + }, string(md), convertedToolCalls) outBytes, err := json.MarshalIndent(out, "", " ") require.NoError(t, err) assertGolden(t, string(outBytes), "slack-tools.golden.json") // Teams - out = msgAIAnswer(openai.Run{}, &Payload{ + out = msgAIAnswer(&Payload{ MessageID: "19:d25cbf7cbfa74d22b42a2918452e1153@thread.tacv2", Prompt: "This is a test", - }, string(md), convertedToolCalls, true) + }, string(md), convertedToolCalls) outBytes, err = json.MarshalIndent(out, "", " ") require.NoError(t, err) assertGolden(t, string(outBytes), "teams-tools.golden.json") - out = msgAIAnswer(openai.Run{}, &Payload{ + out = msgAIAnswer(&Payload{ MessageID: "19:d25cbf7cbfa74d22b42a2918452e1153@thread.tacv2", Prompt: "This is a test two", - }, string(md), convertedToolCalls, false) + }, string(md), convertedToolCalls) outBytes, err = json.MarshalIndent(out, "", " ") require.NoError(t, err) assertGolden(t, string(outBytes), "teams-tools-no-report.golden.json") // Discord and others - out = msgAIAnswer(openai.Run{}, &Payload{ + out = msgAIAnswer(&Payload{ MessageID: "", Prompt: "This is a test", - }, string(md), convertedToolCalls, true) + }, string(md), convertedToolCalls) outBytes, err = json.MarshalIndent(out, "", " ") require.NoError(t, err) assertGolden(t, string(outBytes), "discord-tools.golden.json") diff --git a/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/slack-tools.golden.json b/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/slack-tools.golden.json index e9abbc1c..033f09f5 100644 --- a/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/slack-tools.golden.json +++ b/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/slack-tools.golden.json @@ -22,18 +22,15 @@ "divider": "none" }, "body": {}, - "buttons": [ - { - "description": "AI-generated content may be incorrect.", - "descriptionStyle": "italic", - "name": "🚩Report response", - "command": "{{BotName}} cloud report analytics --bk-cmd-header=\"Report invalid AI response\" -t=ai-invalid-response -f=MESSAGE_ID=\"42.42\" -f=INSTANCE_ID=\"\" -f=RUN_ID=\"\" -f=THREAD_ID=\"\" -f=PROMPT=\"This is a test\"" - } - ], "multiSelect": { "description": {} }, - "selects": {} + "selects": {}, + "context": [ + { + "text": "_AI-generated content may be incorrect._" + } + ] } ], "parentActivityId": "42.42" diff --git a/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/teams-tools.golden.json b/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/teams-tools.golden.json index 31c24aef..25263d48 100644 --- a/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/teams-tools.golden.json +++ b/internal/source/ai-brain/testdata/TestConvertProperlyAIAnswerWithTools/teams-tools.golden.json @@ -4,22 +4,5 @@ "plaintext": "**Found Issues**\n\nThe issue you're facing is due to a missing secret named `ngin`. Secrets in Kubernetes are used to store and handle sensitive information, such as passwords, OAuth tokens, ssh keys, etc. The specific error indicates that a pod is trying to use a secret called `ngin` that doesn’t exist in the cluster. Here’s a simple step-by-step guide on how to fix it:\n\n1. **Identify the Missing Secret Requirement:**\n\n \u003e Ensure that the name **\"ngin\"** is correct. Sometimes, a typo in the pod configuration or secret name can cause such issues.\n\n2. Create the Secret:\n If the secret is indeed missing `##` and you know the data that should be within it (like a token or password), you will need to create the secret. This can be done using the kubectl command line. For example, if you're creating a generic secret, you might use:\n\n ```shell\n kubectl create secret generic ngin --from-literal=key=value -n test\n ```\n\n Replace key and value with the actual data you need to store. The -n test specifies the namespace (test in this case) where the secret will be created. Adjust as necessary.\n3. **Update the Pod or Deployment Configuration:**\n If the secret name was incorrect due to a typo in your pod or deployment configuration, you should update the relevant YAML file to correct the secret name and then apply the changes. For example:\n\n ```yaml\n apiVersion: v1\n kind: Pod\n metadata:\n name: your-pod-name\n spec:\n containers:\n - name: your-container-name\n image: nginx\n env:\n - name: SECRET_KEY\n valueFrom:\n secretKeyRef:\n name: ngin # Make sure this matches the correct secret name\n key: key\n ```\n\nIf you had to update your configuration, apply the changes:\n\n```\nkubectl apply -f your-deployment.yaml\n```\n\n**Ecosystem and Community**\n\n[logo](https://raw.githubusercontent.com/kubeshop/botkube/main/branding/logos/botkube-black-32x32.png)\n\n_Kubernetes_ has a ~~large~~, rapidly growing `ecosystem`. Kubernetes' services, support, and tools are widely available.\n\n\u003e Kubernetes has entered the chat\n\u003e Intelligent Kubernetes Monitoring \u0026 Troubleshooting Platform\n\n[Botkube](https://botkube.io/)\n\n\n| Tables | Are | Cool |\n|----------|:-------------:|------:|\n| col 1 is | left-aligned | $1600 |\n| col 2 is | centered | $12 |\n| col 3 is | right-aligned | $1 |\n\n\n**Notes**\n\nThis message includes:\n- link\n- image\n- text \n - italic\n - bold\n - strike\n- multi-line code block\n - with syntax\n - without syntax\n- single code block\n- bullet list\n- numbered list\n- headers\n- blockquote\n- table\n\n\n~ℹ️ **Sources used:** Botkube agent configuration | Botkube agent status | Botkube and custom content search | kubectl get | kubectl logs~\n\n~AI-generated content may be incorrect.~\n" }, "timestamp": "0001-01-01T00:00:00Z", - "sections": [ - { - "style": {}, - "body": {}, - "buttons": [ - { - "descriptionStyle": "", - "name": "🚩Report response", - "command": "{{BotName}} cloud report analytics --bk-cmd-header=\"Report invalid AI response\" -t=ai-invalid-response -f=MESSAGE_ID=\"19:d25cbf7cbfa74d22b42a2918452e1153@thread.tacv2\" -f=INSTANCE_ID=\"\" -f=RUN_ID=\"\" -f=THREAD_ID=\"\" -f=PROMPT=\"This is a test\"" - } - ], - "multiSelect": { - "description": {} - }, - "selects": {} - } - ], "parentActivityId": "19:d25cbf7cbfa74d22b42a2918452e1153@thread.tacv2" } \ No newline at end of file diff --git a/internal/source/argocd/argocd.go b/internal/source/argocd/argocd.go index 19f99515..41805d4b 100644 --- a/internal/source/argocd/argocd.go +++ b/internal/source/argocd/argocd.go @@ -8,8 +8,6 @@ import ( "sync" "time" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/avast/retry-go/v4" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/source" @@ -62,7 +60,7 @@ func NewSource(version string) source.Source { pluginVersion: version, cfgs: sync.Map{}, } - return auth.NewProtectedSource(src) + return src } type subscription struct { diff --git a/internal/source/github_events/config.go b/internal/source/github_events/config.go index a325a7d4..e72aecb2 100644 --- a/internal/source/github_events/config.go +++ b/internal/source/github_events/config.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events/gh" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events/templates" + "github.com/kubeshop/botkube-plugins/internal/source/github_events/gh" + "github.com/kubeshop/botkube-plugins/internal/source/github_events/templates" "github.com/kubeshop/botkube/pkg/api/source" "github.com/kubeshop/botkube/pkg/config" pluginx "github.com/kubeshop/botkube/pkg/plugin" diff --git a/internal/source/github_events/github_events.go b/internal/source/github_events/github_events.go index cb41ee8d..a1641907 100644 --- a/internal/source/github_events/github_events.go +++ b/internal/source/github_events/github_events.go @@ -13,7 +13,7 @@ import ( "github.com/google/go-querystring/query" "github.com/sirupsen/logrus" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events/templates" + "github.com/kubeshop/botkube-plugins/internal/source/github_events/templates" "github.com/kubeshop/botkube/pkg/api/source" ) diff --git a/internal/source/github_events/render_opts.go b/internal/source/github_events/render_opts.go index c91fe17d..b922859a 100644 --- a/internal/source/github_events/render_opts.go +++ b/internal/source/github_events/render_opts.go @@ -6,7 +6,7 @@ import ( "github.com/Masterminds/sprig/v3" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events/templates" + "github.com/kubeshop/botkube-plugins/internal/source/github_events/templates" "github.com/kubeshop/botkube/pkg/api" ) diff --git a/internal/source/github_events/source_grpc.go b/internal/source/github_events/source_grpc.go index c04bbe84..ef20686d 100644 --- a/internal/source/github_events/source_grpc.go +++ b/internal/source/github_events/source_grpc.go @@ -5,8 +5,7 @@ import ( _ "embed" "fmt" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/kubeshop/botkube-cloud-plugins/internal/source/github_events/gh" + "github.com/kubeshop/botkube-plugins/internal/source/github_events/gh" "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/source" @@ -39,7 +38,7 @@ func NewSource(version string) source.Source { src := &Source{ pluginVersion: version, } - return auth.NewProtectedSource(src) + return src } // Stream streams GitHub events. diff --git a/internal/source/keptn/source.go b/internal/source/keptn/source.go index cdb273be..5226f516 100644 --- a/internal/source/keptn/source.go +++ b/internal/source/keptn/source.go @@ -6,8 +6,6 @@ import ( "fmt" "time" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/source" "github.com/kubeshop/botkube/pkg/loggerx" @@ -42,7 +40,7 @@ func NewSource(version string) source.Source { src := &Source{ pluginVersion: version, } - return auth.NewProtectedSource(src) + return src } // Stream streams Keptn events diff --git a/internal/source/prometheus/source.go b/internal/source/prometheus/source.go index d258492b..41b37880 100644 --- a/internal/source/prometheus/source.go +++ b/internal/source/prometheus/source.go @@ -6,8 +6,6 @@ import ( "fmt" "time" - "github.com/kubeshop/botkube-cloud-plugins/internal/auth" - "github.com/kubeshop/botkube/pkg/api" "github.com/kubeshop/botkube/pkg/api/source" "github.com/kubeshop/botkube/pkg/loggerx" @@ -44,7 +42,7 @@ func NewSource(version string) source.Source { pluginVersion: version, startedAt: time.Now(), } - return auth.NewProtectedSource(src) + return src } // Stream streams prometheus alerts