Skip to content

Commit

Permalink
Changing hook workdir to component; hooks are now always executed as …
Browse files Browse the repository at this point in the history
…absolute path #23 #35
  • Loading branch information
akranga committed Jul 26, 2023
1 parent a0fb439 commit d2d1ee0
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 65 deletions.
53 changes: 32 additions & 21 deletions cmd/hub/ext/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,7 @@ import (
const hubDir = ".hub"

func ExtensionPath(what, args []string) (string, []string, error) {

searchDirs := []string{filepath.Join(".", hubDir)}

customHubDir := os.Getenv("HUB_EXTENSIONS")
if customHubDir != "" {
searchDirs = append(searchDirs, customHubDir)
}

home := os.Getenv("HOME")
homeHubDir := ""
if home != "" {
homeHubDir = filepath.Join(home, hubDir)
searchDirs = append(searchDirs, homeHubDir)
} else {
if config.Verbose {
util.Warn("Unable to lookup $HOME: no home directory set in OS environment")
}
}

searchDirs = append(searchDirs, "/usr/local/share/hub", "/usr/share/hub")

searchDirs := GetExtensionLocations()
for i := len(what); i > 0; i-- {
script := "hub-" + strings.Join(what[0:i], "-")
newArgs := append(what[i:], args...)
Expand Down Expand Up @@ -71,11 +51,22 @@ func ExtensionPath(what, args []string) (string, []string, error) {
}
}

customHubDir := os.Getenv("HUB_EXTENSIONS")
printCustomHubDir := ""
if customHubDir != "" {
printCustomHubDir = fmt.Sprintf(", $HUB_EXTENSIONS=%s", customHubDir)
}

home := os.Getenv("HOME")
homeHubDir := ""
if home != "" {
homeHubDir = filepath.Join(home, hubDir)
} else {
if config.Verbose {
util.Warn("Unable to lookup $HOME: no home directory set in OS environment")
}
}

maybeInstall := ""
if customHubDir == "" && homeHubDir != "" {
_, err := os.Stat(homeHubDir)
Expand All @@ -91,6 +82,26 @@ func ExtensionPath(what, args []string) (string, []string, error) {
return "", nil, fmt.Errorf("Extension not found in %v%s, nor $PATH%s", searchDirs, printCustomHubDir, maybeInstall)
}

func GetExtensionLocations() []string {
results := []string{filepath.Join(".", hubDir)}

customHubDir := os.Getenv("HUB_EXTENSIONS")
if customHubDir != "" {
results = append(results, customHubDir)
}

if os.Getenv("HOME") != "" {
home := os.Getenv("HOME")
hubHome := filepath.Join(home, hubDir)
_, err := os.Stat(hubHome)
if err == nil {
results = append(results, hubHome)
}
}

return append(results, "/usr/local/share/hub", "/usr/share/hub")
}

func RunExtension(what, args []string) {
code, err := runExtension(what, args)
if err != nil {
Expand Down
146 changes: 108 additions & 38 deletions cmd/hub/lifecycle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/google/uuid"

"github.com/epam/hubctl/cmd/hub/config"
"github.com/epam/hubctl/cmd/hub/ext"
"github.com/epam/hubctl/cmd/hub/manifest"
"github.com/epam/hubctl/cmd/hub/parameters"
"github.com/epam/hubctl/cmd/hub/state"
Expand Down Expand Up @@ -446,9 +447,8 @@ NEXT_COMPONENT:
updateStateComponentFailed)
failedComponents = append(failedComponents, componentName)
} else if isDeploy {
rawOutputsCaptured, componentOutputs, dynamicProvides, errs :=
captureOutputs(componentName, componentDir, componentManifest, componentParameters,
stdout, random)
rawOutputsCaptured, componentOutputs, dynamicProvides, errs := captureOutputs(componentName, componentDir, componentManifest, componentParameters,
stdout, random)
rawOutputs = rawOutputsCaptured
if len(errs) > 0 {
log.Printf("Component `%s` failed to %s", componentName, request.Verb)
Expand Down Expand Up @@ -649,29 +649,43 @@ func maybeTestVerb(verb string, test bool) string {
return verb
}

func fireHooks(currentHook string, stackBaseDir string, component *manifest.ComponentRef,
componentParameters parameters.LockedParameters, osEnv []string) ([]byte, []byte, error) {
hooks := findRelevantHooks(currentHook, component.Hooks)
if len(hooks) > 0 {
log.Printf("Running %s hooks:", currentHook)
if len(componentParameters) > 0 {
log.Print("Environment:")
parameters.PrintLockedParameters(componentParameters)
}
func fireHooks(trigger string, stackBaseDir string, component *manifest.ComponentRef,
componentParameters parameters.LockedParameters, osEnv []string,
) ([]byte, []byte, error) {
hooks := findHooksByTrigger(trigger, component.Hooks)
if len(hooks) == 0 {
return nil, nil, nil
}
for i := range hooks {
hook := hooks[i]
filePath := fmt.Sprintf("%s/%s", stackBaseDir, hook.File)
if strings.HasPrefix(hook.File, "/") {
filePath = hook.File

extensions := ext.GetExtensionLocations()
paths := filepath.SplitList(os.Getenv("PATH"))

searchDirs := []string{
component.Source.Dir,
stackBaseDir,
filepath.Join(stackBaseDir, "bin"),
filepath.Join(stackBaseDir, ".hub"),
filepath.Join(stackBaseDir, ".hub", "bin"),
}
searchDirs = append(searchDirs, extensions...)
searchDirs = append(searchDirs, paths...)

for _, hook := range hooks {
var err error
script, err := findScript(hook.File, searchDirs...)
if err != nil || script == "" {
util.Warn("Unable to locate hook script `%s:` %v", hook.File, err)
continue
}
if hook.Brief != "" {
log.Printf("Brief: %s", hook.Brief)
log.Printf("Running %s script: %s", trigger, util.HighlightColor(hook.File))
if config.Verbose && len(componentParameters) > 0 {
log.Print("Environment:")
parameters.PrintLockedParameters(componentParameters)
}
stdout, stderr, err := delegateHook(&hook, stackBaseDir, component, componentParameters, osEnv)
stdout, stderr, err := delegateHook(script, stackBaseDir, component, componentParameters, osEnv)
if err != nil {
if strings.Contains(err.Error(), "fork/exec : no such file or directory") {
log.Printf("Error: file %s has not been found.", filePath)
log.Printf("Error: file %s has not been found.", script)
return stdout, stderr, err
} else if hook.Error == "ignore" {
log.Printf("Error ignored: %s", err.Error())
Expand All @@ -685,13 +699,13 @@ func fireHooks(currentHook string, stackBaseDir string, component *manifest.Comp
return nil, nil, nil
}

func findRelevantHooks(trigger string, hooks []manifest.Hook) []manifest.Hook {
func findHooksByTrigger(trigger string, hooks []manifest.Hook) []manifest.Hook {
matches := make([]manifest.Hook, 0)
for i := range hooks {
hook := hooks[i]
for t := range hook.Triggers {
triggerPattern := hook.Triggers[t]
matched, _ := filepath.Match(triggerPattern, trigger)
glob := hook.Triggers[t]
matched, _ := filepath.Match(glob, trigger)
if matched {
matches = append(matches, hook)
break
Expand All @@ -701,26 +715,72 @@ func findRelevantHooks(trigger string, hooks []manifest.Hook) []manifest.Hook {
return matches
}

func delegateHook(hook *manifest.Hook, stackBaseDir string, component *manifest.ComponentRef, componentParameters parameters.LockedParameters, osEnv []string) ([]byte, []byte, error) {
processEnv := parametersInEnv(component, componentParameters, stackBaseDir)
hookFilePath := fmt.Sprintf("%s/%s", stackBaseDir, hook.File)
if strings.HasPrefix(hook.File, "/") {
hookFilePath = hook.File
func findScript(script string, dirs ...string) (string, error) {
var result string
// special case for absolute filenames - just check if it exists
if filepath.IsAbs(script) {
dir, f := filepath.Split(script)
found, err := probeScript(dir, f)
if err != nil {
return "", err
}
if found == "" {
return "", os.ErrNotExist
}
result = filepath.Join(dir, found)
} else {
var search []string
for _, dir := range dirs {
search = append(search, filepath.Join(dir, script))
}

for _, path := range search {
dir, f := filepath.Split(path)
found, _ := probeScript(dir, f)
if found != "" {
result = filepath.Join(dir, found)
break
}
}
}
hookDir, hookFileName := filepath.Split(hookFilePath)
script, err := probeScript(hookDir, hookFileName)
if err != nil {
return nil, nil, err
if result == "" {
return "", os.ErrNotExist
}
command := &exec.Cmd{Path: script, Dir: hookDir, Env: mergeOsEnviron(osEnv, processEnv)}
stdout, stderr, err := execImplementation(command, false, true)
return stdout, stderr, err
if !filepath.IsAbs(result) {
var err error
result, err = filepath.Abs(result)
if err != nil {
return "", err
}
}
return result, nil
}

func delegateHook(script string, stackDir string, component *manifest.ComponentRef, componentParameters parameters.LockedParameters, osEnv []string) ([]byte, []byte, error) {
var err error
cwd := component.Source.Dir
// components usually stored as relative paths
if !filepath.IsAbs(cwd) {
cwd = filepath.Join(stackDir, cwd)
cwd, err = filepath.Abs(cwd)
if err != nil {
return nil, nil, err
}
}

processEnv := parametersInEnv(component, componentParameters, cwd)
command := &exec.Cmd{
Path: script,
Dir: cwd,
Env: mergeOsEnviron(osEnv, processEnv),
}
return execImplementation(command, false, true)
}

func delegate(verb string, component *manifest.ComponentRef, componentManifest *manifest.Manifest,
componentParameters parameters.LockedParameters,
dir string, osEnv []string, random string, baseDir string) ([]byte, []byte, error) {

dir string, osEnv []string, random string, baseDir string,
) ([]byte, []byte, error) {
if config.Debug && len(componentParameters) > 0 {
log.Print("Component parameters:")
parameters.PrintLockedParameters(componentParameters)
Expand Down Expand Up @@ -823,6 +883,16 @@ func parametersInEnv(component *manifest.ComponentRef, componentParameters param
} else {
componentDir = filepath.Join(baseDir, component.Source.Dir)
}
if !filepath.IsAbs(componentDir) {
t, err := filepath.Abs(componentDir)
if err != nil {
util.Warn("Unable to set absolute path for HUB_COMPONENT_DIR `%s`: %v", component.Name, err)
util.Warn("Falling back to %s directory: %s", component.Name, componentDir)
} else {
componentDir = t
}
}

// for `hub render`
envParameters = append(envParameters, fmt.Sprintf("%s=%s", HubEnvVarNameComponentName, component.Name))
envParameters = append(envParameters, fmt.Sprintf("%s=%s", HubEnvVarNameComponentDir, componentDir))
Expand Down
35 changes: 31 additions & 4 deletions cmd/hub/lifecycle/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
package lifecycle

import (
"os"
"testing"

"github.com/epam/hubctl/cmd/hub/manifest"
"github.com/stretchr/testify/assert"
)

func TestMaybeHooks(t *testing.T) {
func TestHooksFilterByTrigger(t *testing.T) {
hooks := []manifest.Hook{
{
File: "/etc/hook1",
Expand All @@ -35,7 +36,7 @@ func TestMaybeHooks(t *testing.T) {
},
},
}
res := findRelevantHooks("pre-deploy", hooks)
res := findHooksByTrigger("pre-deploy", hooks)
assert.Equal(t, len(res), 2)
hooks = []manifest.Hook{
{
Expand All @@ -58,7 +59,7 @@ func TestMaybeHooks(t *testing.T) {
},
},
}
res = findRelevantHooks("pre-backup", hooks)
res = findHooksByTrigger("pre-backup", hooks)
assert.Equal(t, len(res), 1)
assert.Equal(t, res[0].File, "/etc/hook1")
hooks = []manifest.Hook{
Expand All @@ -82,7 +83,33 @@ func TestMaybeHooks(t *testing.T) {
},
},
}
res = findRelevantHooks("post-deploy", hooks)
res = findHooksByTrigger("post-deploy", hooks)
assert.Equal(t, len(res), 1)
assert.Equal(t, res[0].File, "/etc/hook3")
}

func TestFindScript(t *testing.T) {
temp := t.TempDir()
// always := []string{"*"}
result, err := findScript(temp+"/foo", temp)
assert.Empty(t, result, "It should not return value when script is not found")
assert.Error(t, err, "It should return error when script is not found")

helloWorld := []byte(`#!/bin/sh -e
echo "Hello, World"`)
os.WriteFile(temp+"/bar", helloWorld, 0755)

result, err = findScript(temp+"/bar", temp)
assert.NotEmpty(t, result, "It should return path to the actual file")
assert.FileExists(t, result, "It should return path to the actual file")
assert.NoError(t, err)

result, err = findScript("bar", temp)
assert.FileExists(t, result, "It should return path to the actual file")
assert.NoError(t, err)

os.WriteFile(temp+"/baz.sh", helloWorld, 0755)
result, err = findScript("baz", temp)
assert.FileExists(t, result, "It should return path to the actual file when extension is omitted")
assert.NoError(t, err)
}
4 changes: 2 additions & 2 deletions cmd/hub/lifecycle/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func execImplementation(impl *exec.Cmd, passStdin, paginate bool) ([]byte, []byt
stderrWritter := io.MultiWriter(&stderrBuffer, stderr)
if impl.Path != "" {
dir := impl.Dir
fmt.Printf("--- Dir: %s\n", dir)
fmt.Printf("--- File: %s\n", impl.Path)
fmt.Printf(" Working dir: %s\n", dir)
fmt.Printf(" File: %s\n", impl.Path)
args := ""
if len(impl.Args) > 1 {
args = fmt.Sprintf("Args: %v", impl.Args[1:])
Expand Down

0 comments on commit d2d1ee0

Please sign in to comment.