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 initial version for install and run binaries #1112

Merged
merged 4 commits into from
Jul 5, 2023
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
10 changes: 10 additions & 0 deletions .goreleaser.plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ builds:
goarch: *goarch
goarm: *goarm

- id: x
main: cmd/executor/x/main.go
binary: executor_x_{{ .Os }}_{{ .Arch }}

no_unique_dist_dir: true
env: *env
goos: *goos
goarch: *goarch
goarm: *goarm

- id: gh
main: cmd/executor/gh/main.go
binary: executor_gh_{{ .Os }}_{{ .Arch }}
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ For faster development, you can also build and run Botkube outside K8s cluster.
> **Note**
> Each time you make a change to the [source](cmd/source) or [executors](cmd/executor) plugins re-run the above command.

> **Note**
> To build specific plugin binaries, use `PLUGIN_TARGETS`. For example `PLUGIN_TARGETS="x, kubectl" make build-plugins-single`.

## Making A Change

- Before making any significant changes, please [open an issue](https://github.com/kubeshop/botkube/issues). Discussing your proposed changes ahead of time will make the contribution process smooth for everyone.
Expand Down
7 changes: 6 additions & 1 deletion build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.15
FROM alpine:3.18
ARG botkube_version="dev"
LABEL org.opencontainers.image.source="[email protected]:kubeshop/botkube.git" \
org.opencontainers.image.title="Botkube" \
Expand All @@ -9,6 +9,11 @@ LABEL org.opencontainers.image.source="[email protected]:kubeshop/botkube.git" \

COPY botkube /usr/local/bin/botkube

RUN apk add --no-cache 'git=>2.38' 'openssh=~9.3' && \
mkdir /root/.ssh && \
chmod 700 /root/.ssh && \
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts

# Create Non Privileged user
RUN addgroup --gid 1001 botkube && \
adduser -S --uid 1001 --ingroup botkube botkube
Expand Down
10 changes: 7 additions & 3 deletions cmd/executor/gh/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ func createGitHubIssue(cfg Config, title, mdBody string) (string, error) {
"GH_TOKEN": cfg.GitHub.Token,
}

return pluginx.ExecuteCommandWithEnvs(context.Background(), cmd, envs)
output, err := pluginx.ExecuteCommand(context.Background(), cmd, pluginx.ExecuteCommandEnvs(envs))
if err != nil {
return "", err
}
return output.Stdout, nil
}

// IssueDetails holds all available information about a given issue.
Expand Down Expand Up @@ -190,8 +194,8 @@ func getIssueDetails(ctx context.Context, namespace, name, kubeConfigPath string
return IssueDetails{
Type: name,
Namespace: namespace,
Logs: logs,
Version: ver,
Logs: logs.Stdout,
Version: ver.Stdout,
}, nil
}

Expand Down
240 changes: 240 additions & 0 deletions cmd/executor/x/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package main
pkosiec marked this conversation as resolved.
Show resolved Hide resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I'd follow-up with your idea about wrapping such binaries + templates in dedicated plugins as it could be hard for someone to use flux etc. 🤔 Do we have an issue for it already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can take this and discuss inside our team 👍

import (
"context"
"fmt"
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/alexflint/go-arg"
"github.com/hashicorp/go-plugin"
"github.com/sirupsen/logrus"

"github.com/kubeshop/botkube/internal/executor/x"
"github.com/kubeshop/botkube/internal/executor/x/getter"
"github.com/kubeshop/botkube/internal/executor/x/output"
"github.com/kubeshop/botkube/internal/executor/x/state"
"github.com/kubeshop/botkube/internal/loggerx"
"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/api/executor"
"github.com/kubeshop/botkube/pkg/formatx"
"github.com/kubeshop/botkube/pkg/pluginx"
)

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

const pluginName = "x"

// XExecutor implements Botkube executor plugin.
type XExecutor struct{}

func (i *XExecutor) Help(_ context.Context) (api.Message, error) {
help := heredoc.Doc(`
Usage:
x run [COMMAND] [FLAGS] Run a specified command with optional flags
x install [SOURCE] Install a binary using the https://github.com/zyedidia/eget syntax.

Usage Examples:
# Install the Helm CLI

x install https://get.helm.sh/helm-v3.10.3-linux-amd64.tar.gz --file helm

# Run the 'helm list -A' command.

x run helm list -A

Options:
-h, --help Show this help message`)
return api.NewCodeBlockMessage(help, true), nil
}

// Metadata returns details about Echo plugin.
func (*XExecutor) Metadata(context.Context) (api.MetadataOutput, error) {
return api.MetadataOutput{
Version: version,
Description: "Install and run CLIs directly from chat window without hassle. All magic included.",
Dependencies: x.GetPluginDependencies(),
JSONSchema: jsonSchema(),
}, nil
}

type (
Commands struct {
Install *InstallCmd `arg:"subcommand:install"`
Run *RunCmd `arg:"subcommand:run"`
}
InstallCmd struct {
Tool []string `arg:"positional"`
}
RunCmd struct {
Tool []string `arg:"positional"`
}
)

func escapePositionals(in string) string {
for _, name := range []string{"run", "install"} {
if strings.Contains(in, name) {
return strings.Replace(in, name, fmt.Sprintf("%s -- ", name), 1)
}
}
return in
}

// Execute returns a given command as response.
//
//nolint:gocritic // hugeParam: in is heavy (80 bytes); consider passing it by pointer
func (i *XExecutor) Execute(ctx context.Context, in executor.ExecuteInput) (executor.ExecuteOutput, error) {
var cmd Commands
in.Command = escapePositionals(in.Command)
err := pluginx.ParseCommand(pluginName, in.Command, &cmd)
switch err {
case nil:
case arg.ErrHelp:
msg, _ := i.Help(ctx)
return executor.ExecuteOutput{
Message: msg,
}, nil
default:
return executor.ExecuteOutput{}, fmt.Errorf("while parsing input command: %w", err)
}

cfg := x.Config{
Templates: []getter.Source{
{Ref: getDefaultTemplateSource()},
},
}
if err := pluginx.MergeExecutorConfigs(in.Configs, &cfg); err != nil {
return executor.ExecuteOutput{}, err
}

log := loggerx.New(cfg.Logger)

renderer := x.NewRenderer()
err = renderer.RegisterAll(map[string]x.Render{
"parser:table:.*": output.NewTableCommandParser(log),
})
if err != nil {
return executor.ExecuteOutput{}, err
}

switch {
case cmd.Run != nil:
tool := Normalize(strings.Join(cmd.Run.Tool, " "))
log.WithField("tool", tool).Info("Running command...")

state := state.ExtractSlackState(in.Context.SlackState)

kubeConfigPath, deleteFn, err := i.getKubeconfig(ctx, log, in)
defer deleteFn()
if err != nil {
return executor.ExecuteOutput{}, err
}

return x.NewRunner(log, renderer).Run(ctx, cfg, state, tool, kubeConfigPath)
case cmd.Install != nil:
var (
tool = Normalize(strings.Join(cmd.Install.Tool, " "))
dir, isCustom = cfg.TmpDir.Get()
downloadCmd = fmt.Sprintf("eget %s", tool)
)

log.WithFields(logrus.Fields{
"dir": dir,
"isCustom": isCustom,
"downloadCmd": downloadCmd,
}).Info("Installing binary...")

if _, err := pluginx.ExecuteCommand(ctx, downloadCmd, pluginx.ExecuteCommandEnvs(map[string]string{
"EGET_BIN": dir,
})); err != nil {
return executor.ExecuteOutput{}, err
}

return executor.ExecuteOutput{
Message: api.NewPlaintextMessage("Binary was installed successfully", false),
}, nil
}
return executor.ExecuteOutput{
Message: api.NewPlaintextMessage("Command not supported", false),
}, nil
}

func (i *XExecutor) getKubeconfig(ctx context.Context, log logrus.FieldLogger, in executor.ExecuteInput) (string, func(), error) {
if len(in.Context.KubeConfig) == 0 {
return "", func() {}, nil
}
kubeConfigPath, deleteFn, err := pluginx.PersistKubeConfig(ctx, in.Context.KubeConfig)
if err != nil {
return "", func() {}, fmt.Errorf("while writing kubeconfig file: %w", err)
}

return kubeConfigPath, func() {
err := deleteFn(ctx)
if err != nil {
log.WithError(err).WithField("kubeconfigPath", kubeConfigPath).Error("Failed to delete kubeconfig file")
}
}, nil
}

func main() {
executor.Serve(map[string]plugin.Plugin{
pluginName: &executor.Plugin{
Executor: &XExecutor{},
},
})
}

// jsonSchema returns JSON schema for the executor.
func jsonSchema() api.JSONSchema {
return api.JSONSchema{
Value: heredoc.Docf(`{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "x",
"description": "Install and run CLIs directly from the chat window without hassle. All magic included.",
"type": "object",
"properties": {
"templates": {
"type": "array",
mszostok marked this conversation as resolved.
Show resolved Hide resolved
"title": "List of templates",
"description": "An array of templates that define how to convert the command output into an interactive message.",
"items": {
"type": "object",
"properties": {
"ref": {
"title": "Link to templates source",
"description": "It uses the go-getter library, which supports multiple URL formats (such as HTTP, Git repositories, or S3) and is able to unpack archives. For more details, see the documentation at https://github.com/hashicorp/go-getter.",
"type": "string",
"default": ""
}
},
"required": [
"ref"
],
"additionalProperties": false
}
}
},
"required": [
"templates"
]
}`, getDefaultTemplateSource()),
}
}

func getDefaultTemplateSource() string {
ver := version
if ver == "dev" {
ver = "main"
}
return fmt.Sprintf("github.com/kubeshop/botkube//cmd/executor/x/templates?ref=%s", ver)
}

func Normalize(in string) string {
out := formatx.RemoveHyperlinks(in)
out = strings.NewReplacer(`“`, `"`, `”`, `"`, `‘`, `"`, `’`, `"`).Replace(out)

out = strings.TrimSpace(out)

return out
}
12 changes: 12 additions & 0 deletions cmd/executor/x/templates/argo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
templates:
- command:
prefix: "argo list"
parser: "table"
message:
select:
name: "Workflows"
itemKey: "{{ .Namespace }}/{{ .Name }}"
actions:
logs: "argo logs {{ .Name }} -n {{ .Namespace }}"
describe: "argo get {{ .Name }} -n {{ .Namespace }}"
delete: "argo delete {{ .Name }} -n {{ .Namespace }}"
16 changes: 16 additions & 0 deletions cmd/executor/x/templates/flux.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
templates:
- trigger:
command: "flux get sources"
type: "parser:table:space"
message:
selects:
- name: "Source"
keyTpl: "{{ .Name }}"
actions:
export: "flux export source git {{ .Name }}"
preview: |
Name: {{ .Name }}
Revision: {{ .Revision }}
Suspended: {{ .Suspended }}
Ready: {{ .Ready }}
Message: {{ .Message}}
17 changes: 17 additions & 0 deletions cmd/executor/x/templates/helm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
templates:
- trigger:
command: "helm list"
type: "parser:table:space"
message:
selects:
- name: "Release"
keyTpl: "{{ .Namespace }}/{{ .Name }}"
actions:
notes: "helm get notes {{ .Name }} -n {{ .Namespace }}"
values: "helm get values {{ .Name }} -n {{ .Namespace }}"
delete: "helm delete {{ .Name }} -n {{ .Namespace }}"
preview: |
Name: {{ .Name }}
Namespace: {{ .Namespace }}
Status: {{ .Status }}
Chart: {{ .Chart }}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/hashicorp/go-plugin v1.4.8
github.com/hashicorp/go-version v1.6.0
github.com/hasura/go-graphql-client v0.8.1
github.com/huandu/xstrings v1.3.2
github.com/infracloudio/msbotbuilder-go v0.2.5
github.com/knadh/koanf v1.4.4
github.com/mattermost/mattermost-server/v5 v5.39.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4Dvx
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
Expand Down
Loading