Skip to content

Commit

Permalink
expand on run executable to include file execution and parameter expa…
Browse files Browse the repository at this point in the history
…nsion
  • Loading branch information
jahvon committed Oct 9, 2023
1 parent af56a42 commit 4674b9b
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 33 deletions.
4 changes: 3 additions & 1 deletion cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ var getWorkspaceCmd = &cobra.Command{
if rootCfg == nil {
log.Fatal().Msg("failed to load config")
}
io.PrintNotice(rootCfg.CurrentWorkspace)
wsPath := rootCfg.Workspaces[rootCfg.CurrentWorkspace]
wsInfo := fmt.Sprintf("%s (%s)", rootCfg.CurrentWorkspace, wsPath)
io.PrintNotice(wsInfo)
},
}

Expand Down
6 changes: 2 additions & 4 deletions internal/config/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"fmt"
"path/filepath"

"github.com/jahvon/flow/internal/workspace"
)
Expand All @@ -17,12 +16,11 @@ func SetCurrentWorkspace(config *RootConfig, name string) error {
}

func SetWorkspace(config *RootConfig, name, location string) error {
workspaceDir := filepath.Join(location, name)
if err := workspace.SetDirectory(workspaceDir); err != nil {
if err := workspace.SetDirectory(location); err != nil {
return err
}

config.Workspaces[name] = workspaceDir
config.Workspaces[name] = location
return writeConfigFile(config)
}

Expand Down
12 changes: 7 additions & 5 deletions internal/executable/executable.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ type Executable struct {
Tags []string `yaml:"tags"`
Spec map[string]interface{} `yaml:"spec"`

workspace string
namespace string
workspace string
workspacePath string
namespace string
}

func (e *Executable) SetContext(workspace, namespace string) {
func (e *Executable) SetContext(workspace, workspacePath, namespace string) {
e.workspace = workspace
e.workspacePath = workspacePath
e.namespace = namespace
}

func (e *Executable) GetContext() (workspace string, namespace string) {
return e.workspace, e.namespace
func (e *Executable) GetContext() (workspace, workspacePath, namespace string) {
return e.workspace, e.workspacePath, e.namespace
}

func (e *Executable) ID() string {
Expand Down
17 changes: 13 additions & 4 deletions internal/executable/parameter/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"regexp"
"strings"

"github.com/jahvon/flow/internal/io"
"github.com/jahvon/flow/internal/vault"
)

type Parameter struct {
// Only one of text or secretRef should be set.
// Only one of text, secretRef, or prompt should be set.
Text string `yaml:"text"`
Prompt string `yaml:"prompt"`
SecretRef string `yaml:"secretRef"`

EnvKey string `yaml:"envKey"`
Expand All @@ -22,10 +24,14 @@ const (
)

func (p *Parameter) Validate() error {
if p.Text == "" && p.SecretRef == "" {
return errors.New("must set either text or secretRef for parameter")
if p.Text == "" && p.SecretRef == "" && p.Prompt == "" {
return errors.New("must set either text, secretRef, or prompt for parameter")
} else if p.Text != "" && p.SecretRef != "" {
return errors.New("cannot set both text and secretRef for parameter")
} else if p.Text != "" && p.Prompt != "" {
return errors.New("cannot set both text and prompt for parameter")
} else if p.SecretRef != "" && p.Prompt != "" {
return errors.New("cannot set both secretRef and prompt for parameter")
}

if p.EnvKey == "" {
Expand All @@ -43,10 +49,13 @@ func (p *Parameter) Validate() error {
}

func (p *Parameter) Value() (string, error) {
if p.Text == "" && p.SecretRef == "" {
if p.Text == "" && p.SecretRef == "" && p.Prompt == "" {
return "", nil
} else if p.Text != "" {
return p.Text, nil
} else if p.Prompt != "" {
response := io.Ask(p.Prompt)
return response, nil
}

if err := vault.ValidateReference(p.SecretRef); err != nil {
Expand Down
37 changes: 30 additions & 7 deletions internal/executable/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package run

import (
"fmt"
"strings"

"github.com/mitchellh/mapstructure"

Expand All @@ -15,10 +16,12 @@ type agent struct {
}

type Spec struct {
Timeout string `yaml:"timeout" mapstructure:"timeout"`
CommandStr string `yaml:"cmd" mapstructure:"cmd"`
Dir string `yaml:"dir" mapstructure:"dir"`
Params []parameter.Parameter `yaml:"params" mapstructure:"params"`
Timeout string `yaml:"timeout" mapstructure:"timeout"`
Dir string `yaml:"dir" mapstructure:"dir"`
Params []parameter.Parameter `yaml:"params" mapstructure:"params"`

CommandStr string `yaml:"cmd" mapstructure:"cmd"`
ShFile string `yaml:"file" mapstructure:"file"`
}

func NewAgent() executable.Agent {
Expand All @@ -39,11 +42,31 @@ func (a *agent) Exec(exec executable.Executable) error {
return fmt.Errorf("unable to decode 'run' executable spec - %v", err)
}

params := runSpec.Params
envList, err := parameter.ParameterListToEnvList(params)
if err != nil {
return fmt.Errorf("unable to convert parameters to env list - %v", err)
}

targetDir := runSpec.Dir
if targetDir == "" {
_, workspacePath, _ := exec.GetContext()
targetDir = workspacePath
} else if strings.HasPrefix(targetDir, "//") {
_, workspacePath, _ := exec.GetContext()
targetDir = strings.Replace(targetDir, "//", workspacePath+"/", 1)
}

err = executable.WithTimeout(runSpec.Timeout, func() error {
if runSpec.Dir != "" {
return run.RunCmdIn(runSpec.CommandStr, runSpec.Dir)
if runSpec.CommandStr != "" && runSpec.ShFile != "" {
return fmt.Errorf("cannot set both cmd and file")
} else if runSpec.CommandStr != "" {
return run.RunCmd(runSpec.CommandStr, targetDir, envList)
} else if runSpec.ShFile != "" {
return run.RunFile(runSpec.ShFile, targetDir, envList)
} else {
return fmt.Errorf("either cmd or file must be specified")
}
return run.RunCmd(runSpec.CommandStr)
})

return err
Expand Down
4 changes: 4 additions & 0 deletions internal/executable/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
)

func WithTimeout(timeoutStr string, fn func() error) error {
if timeoutStr == "" {
return fn()
}

timeout, err := time.ParseDuration(timeoutStr)
if err != nil {
return fmt.Errorf("unable to parse timeout duration - %v", err)
Expand Down
3 changes: 2 additions & 1 deletion internal/io/executable/executable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"strings"

"github.com/pterm/pterm"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"

"github.com/jahvon/flow/internal/executable"
"github.com/jahvon/flow/internal/io"
)

var log = io.Log()

type executableListOutput struct {
Executables []*executableOutput `json:"executables"`
}
Expand Down
57 changes: 50 additions & 7 deletions internal/services/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"mvdan.cc/sh/v3/expand"
Expand All @@ -15,11 +16,8 @@ import (

var log = io.Log()

func RunCmd(commandStr string) error {
return RunCmdIn(commandStr, "")
}

func RunCmdIn(commandStr, dir string) error {
// RunCmd executes a command in the current shell in a specific directory
func RunCmd(commandStr, dir string, envList []string) error {
log.Trace().Msgf("running command (%s) in dir (%s)", commandStr, dir)

ctx := context.Background()
Expand All @@ -30,10 +28,14 @@ func RunCmdIn(commandStr, dir string) error {
return fmt.Errorf("unable to parse command - %v", err)
}

if envList == nil {
envList = make([]string, 0)
}
envList = append(os.Environ(), envList...)

runner, err := interp.New(
interp.Dir(dir),
// TODO: accept configured env vars
interp.Env(expand.ListEnviron(os.Environ()...)),
interp.Env(expand.ListEnviron(envList...)),
interp.StdIO(
io.StdInReader{},
io.StdOutWriter{LogAsDebug: false},
Expand All @@ -46,3 +48,44 @@ func RunCmdIn(commandStr, dir string) error {
}
return nil
}

// RunFile executes a file in the current shell in a specific directory
func RunFile(filename, dir string, envList []string) error {
log.Trace().Msgf("executing file (%s)", filename)

ctx := context.Background()
fullPath := filepath.Join(dir, filename)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
return fmt.Errorf("file does not exist - %s", fullPath)
}
file, err := os.OpenFile(fullPath, os.O_RDONLY, 0)

Check failure

Code scanning / gosec

Potential file inclusion via variable Error

Potential file inclusion via variable
if err != nil {
return fmt.Errorf("unable to open file - %v", err)
}
defer file.Close()

parser := syntax.NewParser()
prog, err := parser.Parse(file, "")
if err != nil {
return fmt.Errorf("unable to parse file - %v", err)
}

if envList == nil {
envList = make([]string, 0)
}
envList = append(os.Environ(), envList...)

runner, err := interp.New(
interp.Env(expand.ListEnviron(envList...)),
interp.StdIO(
io.StdInReader{},
io.StdOutWriter{LogAsDebug: false},
io.StdErrWriter{LogAsDebug: false},
),
)
err = runner.Run(ctx, prog)
if err != nil {
return fmt.Errorf("encountered an error executing file - %v", err)
}
return nil
}
10 changes: 6 additions & 4 deletions internal/workspace/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ type Definition struct {
Tags []string `yaml:"tags"`
Executables executable.List `yaml:"executables"`

workspace string
workspace string
workspacePath string
}

func (d *Definition) SetContext(workspace string) {
func (d *Definition) SetContext(workspace, workspacePath string) {
d.workspace = workspace
d.workspacePath = workspacePath
for _, exec := range d.Executables {
exec.SetContext(workspace, d.Namespace)
exec.SetContext(workspace, workspacePath, d.Namespace)
}
}

Expand Down Expand Up @@ -91,7 +93,7 @@ func LoadDefinitions(workspace, workspacePath string) (DefinitionList, error) {
if err != nil {
return nil, err
}
definition.SetContext(workspace)
definition.SetContext(workspace, workspacePath)
definitions = append(definitions, definition)
}

Expand Down

0 comments on commit 4674b9b

Please sign in to comment.