From b9aa6e73dd80ad5439f3f6de5a03ec8c1e26066a Mon Sep 17 00:00:00 2001 From: Mateusz Szostok Date: Tue, 4 Jul 2023 11:51:21 +0200 Subject: [PATCH] Apply fixes after review --- cmd/executor/x/main.go | 25 +++++++-- internal/executor/x/getter/download.go | 2 - internal/executor/x/getter/load.go | 38 -------------- internal/executor/x/getter/walk.go | 53 ++++++++++++++++++++ internal/executor/x/output/message_parser.go | 9 ++-- internal/plugin/tmp_dir.go | 3 +- pkg/pluginx/command.go | 12 +++++ 7 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 internal/executor/x/getter/walk.go diff --git a/cmd/executor/x/main.go b/cmd/executor/x/main.go index 44a088035..e0fd2ae70 100644 --- a/cmd/executor/x/main.go +++ b/cmd/executor/x/main.go @@ -11,6 +11,7 @@ import ( "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" @@ -98,7 +99,11 @@ func (i *XExecutor) Execute(ctx context.Context, in executor.ExecuteInput) (exec return executor.ExecuteOutput{}, fmt.Errorf("while parsing input command: %w", err) } - var cfg x.Config + cfg := x.Config{ + Templates: []getter.Source{ + {Ref: getDefaultTemplateSource()}, + }, + } if err := pluginx.MergeExecutorConfigs(in.Configs, &cfg); err != nil { return executor.ExecuteOutput{}, err } @@ -186,17 +191,21 @@ func jsonSchema() api.JSONSchema { Value: heredoc.Docf(`{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "x", - "description": "Install and run CLIs directly from chat window without hassle. All magic included.", + "description": "Install and run CLIs directly from the chat window without hassle. All magic included.", "type": "object", "properties": { "templates": { "type": "array", + "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": "github.com/mszostok/botkube-plugins//x-templates?ref=hackathon" + "default": "" } }, "required": [ @@ -209,8 +218,16 @@ func jsonSchema() api.JSONSchema { "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 { diff --git a/internal/executor/x/getter/download.go b/internal/executor/x/getter/download.go index 9f2bcc863..6239658dc 100644 --- a/internal/executor/x/getter/download.go +++ b/internal/executor/x/getter/download.go @@ -4,7 +4,6 @@ import ( "context" "crypto/sha256" "encoding/base64" - "fmt" "os" "path/filepath" ) @@ -37,7 +36,6 @@ func runIfFileDoesNotExist(path string, fn func() error) error { _, err := os.Stat(path) switch { case err == nil: - fmt.Println("already downloaded") case os.IsNotExist(err): return fn() default: diff --git a/internal/executor/x/getter/load.go b/internal/executor/x/getter/load.go index 05d233d36..eca3c43a1 100644 --- a/internal/executor/x/getter/load.go +++ b/internal/executor/x/getter/load.go @@ -55,41 +55,3 @@ func Load[T any](ctx context.Context, tmpDir string, templateSources []Source) ( return out, nil } - -// symwalkFunc calls the provided WalkFn for regular files. -// However, when it encounters a symbolic link, it resolves the link fully using the -// filepath.EvalSymlinks function and recursively calls symwalk.Walk on the resolved path. -// This ensures that unlink filepath.Walk, traversal does not stop at symbolic links. -// -// Note that symwalk.Walk does not terminate if there are any non-terminating loops in -// the file structure. -func walk(filename string, linkDirname string, walkFn fs.WalkDirFunc) error { - return filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error { - if fname, err := filepath.Rel(filename, path); err == nil { - path = filepath.Join(linkDirname, fname) - } else { - return err - } - - if err == nil && d.Type()&os.ModeSymlink == os.ModeSymlink { - finalPath, err := filepath.EvalSymlinks(path) - if err != nil { - return err - } - info, err := os.Lstat(finalPath) - if err != nil { - return walkFn(path, d, err) - } - if info.IsDir() { - return walk(finalPath, path, walkFn) - } - } - - return walkFn(path, d, err) - }) -} - -// Walk extends filepath.Walk to also follow symlinks -func Walk(path string, walkFn fs.WalkDirFunc) error { - return walk(path, path, walkFn) -} diff --git a/internal/executor/x/getter/walk.go b/internal/executor/x/getter/walk.go new file mode 100644 index 000000000..d223c9842 --- /dev/null +++ b/internal/executor/x/getter/walk.go @@ -0,0 +1,53 @@ +// Package getter. +// +// Code copied from: https://github.com/facebookarchive/symwalk/blob/42004b9f322246749dd73ad71008b1f3160c0052/walk.go#L12-L45 +// BSD License +// +// # For symwalk software +// +// Copyright (c) 2015, Facebook, Inc. All rights reserved. +package getter + +import ( + "io/fs" + "os" + "path/filepath" +) + +// symwalkFunc calls the provided WalkFn for regular files. +// However, when it encounters a symbolic link, it resolves the link fully using the +// filepath.EvalSymlinks function and recursively calls symwalk.Walk on the resolved path. +// This ensures that unlink filepath.Walk, traversal does not stop at symbolic links. +// +// Note that symwalk.Walk does not terminate if there are any non-terminating loops in +// the file structure. +func walk(filename string, linkDirname string, walkFn fs.WalkDirFunc) error { + return filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error { + if fname, err := filepath.Rel(filename, path); err == nil { + path = filepath.Join(linkDirname, fname) + } else { + return err + } + + if err == nil && d.Type()&os.ModeSymlink == os.ModeSymlink { + finalPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + info, err := os.Lstat(finalPath) + if err != nil { + return walkFn(path, d, err) + } + if info.IsDir() { + return walk(finalPath, path, walkFn) + } + } + + return walkFn(path, d, err) + }) +} + +// Walk extends filepath.Walk to also follow symlinks +func Walk(path string, walkFn fs.WalkDirFunc) error { + return walk(path, path, walkFn) +} diff --git a/internal/executor/x/output/message_parser.go b/internal/executor/x/output/message_parser.go index c65db06a1..b9f17ec4f 100644 --- a/internal/executor/x/output/message_parser.go +++ b/internal/executor/x/output/message_parser.go @@ -100,7 +100,7 @@ func (p *TableCommandParser) renderActions(msgCtx template.ParseMessage, table p return api.Section{ Buttons: []api.Button{ - btnBuilder.ForCommandWithoutDesc("Raw output", fmt.Sprintf("x run %s %s", cmd, x.RawOutputIndicator)), + btnBuilder.ForCommand("Raw output", fmt.Sprintf("x run %s %s", cmd, x.RawOutputIndicator)), }, Selects: api.Selects{ Items: []api.Select{ @@ -244,14 +244,17 @@ func (*TableCommandParser) resolveSelectIdx(state *state.Container, selectID str return val } -func (*TableCommandParser) renderGoTemplate(tpl string, cols, rows []string) (string, error) { +func (p *TableCommandParser) renderGoTemplate(tpl string, cols, rows []string) (string, error) { data := map[string]string{} for idx, col := range cols { col = xstrings.ToCamelCase(strings.ToLower(col)) data[col] = rows[idx] } - fmt.Println(data) + p.log.WithFields(logrus.Fields{ + "tpl": tpl, + "data": data, + }).Debug("Rendering Go template") tmpl, err := gotemplate.New("tpl").Parse(tpl) if err != nil { diff --git a/internal/plugin/tmp_dir.go b/internal/plugin/tmp_dir.go index c42986d53..69ff6e166 100644 --- a/internal/plugin/tmp_dir.go +++ b/internal/plugin/tmp_dir.go @@ -2,6 +2,7 @@ package plugin import ( "os" + "path" ) type TmpDir string @@ -16,7 +17,7 @@ func (t TmpDir) Get() (string, bool) { return depDir, false } - return "/tmp/bin", true + return path.Join(os.TempDir(), "bin"), true } func (t TmpDir) GetDirectory() string { diff --git a/pkg/pluginx/command.go b/pkg/pluginx/command.go index 7adac531e..7cf538e1a 100644 --- a/pkg/pluginx/command.go +++ b/pkg/pluginx/command.go @@ -53,6 +53,18 @@ type ExecuteCommandOutput struct { ExitCode int } +// ExecuteCommandWithEnvs is a simple wrapper around exec.CommandContext to simplify running a given +// command. +// +// Deprecated: Use ExecuteCommand(ctx, rawCmd, ExecuteCommandEnvs(envs)) instead. +func ExecuteCommandWithEnvs(ctx context.Context, rawCmd string, envs map[string]string) (string, error) { + out, err := ExecuteCommand(ctx, rawCmd, ExecuteCommandEnvs(envs)) + if err != nil { + return "", err + } + return out.Stdout, nil +} + // ExecuteCommand is a simple wrapper around exec.CommandContext to simplify running a given command. func ExecuteCommand(ctx context.Context, rawCmd string, mutators ...ExecuteCommandMutation) (ExecuteCommandOutput, error) { opts := ExecuteCommandOptions{