mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-24 20:35:03 -07:00
feat(golang-rewrite): create hooks
* Update tests for config package to use assert package * Create Config.GetHook method * Create execute package for running Bash commands * Create hook package for running asdf hooks * Run plugin add hooks
This commit is contained in:
parent
44e8257dcf
commit
b1a47fe2bf
@ -32,6 +32,7 @@ var pluginRepoCheckDurationDefault = PluginRepoCheckDuration{Every: 60}
|
||||
// Settings is a struct that stores config values from the asdfrc file
|
||||
type Settings struct {
|
||||
Loaded bool
|
||||
Raw *ini.Section
|
||||
LegacyVersionFile bool
|
||||
// I don't think this setting should be supported in the Golang implementation
|
||||
// UseReleaseCandidates bool
|
||||
@ -56,6 +57,7 @@ type Config struct {
|
||||
func defaultSettings() *Settings {
|
||||
return &Settings{
|
||||
Loaded: false,
|
||||
Raw: nil,
|
||||
LegacyVersionFile: false,
|
||||
AlwaysKeepDownload: false,
|
||||
PluginRepositoryLastCheckDuration: pluginRepoCheckDurationDefault,
|
||||
@ -142,6 +144,16 @@ func (c *Config) DisablePluginShortNameRepository() (bool, error) {
|
||||
return c.Settings.DisablePluginShortNameRepository, nil
|
||||
}
|
||||
|
||||
// GetHook returns a hook command from config if it is there
|
||||
func (c *Config) GetHook(hook string) (string, error) {
|
||||
err := c.loadSettings()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return c.Settings.Raw.Key(hook).String(), nil
|
||||
}
|
||||
|
||||
func (c *Config) loadSettings() error {
|
||||
if c.Settings.Loaded {
|
||||
return nil
|
||||
@ -191,6 +203,7 @@ func loadSettings(asdfrcPath string) (Settings, error) {
|
||||
mainConf := config.Section("")
|
||||
|
||||
settings := defaultSettings()
|
||||
settings.Raw = mainConf
|
||||
|
||||
settings.Loaded = true
|
||||
settings.PluginRepositoryLastCheckDuration = newPluginRepoCheckDuration(mainConf.Key("plugin_repository_last_check_duration").String())
|
||||
|
@ -2,22 +2,24 @@ package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
config, err := LoadConfig()
|
||||
|
||||
assert(t, err == nil, "Returned error when building config")
|
||||
assert.Nil(t, err, "Returned error when building config")
|
||||
|
||||
assert(t, config.Home != "", "Expected Home to be set")
|
||||
assert.NotZero(t, config.Home, "Expected Home to be set")
|
||||
}
|
||||
|
||||
func TestLoadConfigEnv(t *testing.T) {
|
||||
config, err := loadConfigEnv()
|
||||
|
||||
assert(t, err == nil, "Returned error when loading env for config")
|
||||
assert.Nil(t, err, "Returned error when loading env for config")
|
||||
|
||||
assert(t, config.Home == "", "Shouldn't set Home property when loading config")
|
||||
assert.Zero(t, config.Home, "Shouldn't set Home property when loading config")
|
||||
}
|
||||
|
||||
func TestLoadSettings(t *testing.T) {
|
||||
@ -36,26 +38,26 @@ func TestLoadSettings(t *testing.T) {
|
||||
t.Run("When given path to populated asdfrc returns populated settings struct", func(t *testing.T) {
|
||||
settings, err := loadSettings("testdata/asdfrc")
|
||||
|
||||
refuteError(t, err)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert(t, settings.Loaded, "Expected Loaded field to be set to true")
|
||||
assert(t, settings.LegacyVersionFile == true, "LegacyVersionFile field has wrong value")
|
||||
assert(t, settings.AlwaysKeepDownload == true, "AlwaysKeepDownload field has wrong value")
|
||||
assert(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert(t, settings.PluginRepositoryLastCheckDuration.Every == 0, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert(t, settings.DisablePluginShortNameRepository == true, "DisablePluginShortNameRepository field has wrong value")
|
||||
assert.True(t, settings.Loaded, "Expected Loaded field to be set to true")
|
||||
assert.True(t, settings.LegacyVersionFile, "LegacyVersionFile field has wrong value")
|
||||
assert.True(t, settings.AlwaysKeepDownload, "AlwaysKeepDownload field has wrong value")
|
||||
assert.True(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert.Zero(t, settings.PluginRepositoryLastCheckDuration.Every, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert.True(t, settings.DisablePluginShortNameRepository, "DisablePluginShortNameRepository field has wrong value")
|
||||
})
|
||||
|
||||
t.Run("When given path to empty file returns settings struct with defaults", func(t *testing.T) {
|
||||
settings, err := loadSettings("testdata/empty-asdfrc")
|
||||
|
||||
refuteError(t, err)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert(t, settings.LegacyVersionFile == false, "LegacyVersionFile field has wrong value")
|
||||
assert(t, settings.AlwaysKeepDownload == false, "AlwaysKeepDownload field has wrong value")
|
||||
assert(t, settings.PluginRepositoryLastCheckDuration.Never == false, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert(t, settings.PluginRepositoryLastCheckDuration.Every == 60, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert(t, settings.DisablePluginShortNameRepository == false, "DisablePluginShortNameRepository field has wrong value")
|
||||
assert.False(t, settings.LegacyVersionFile, "LegacyVersionFile field has wrong value")
|
||||
assert.False(t, settings.AlwaysKeepDownload, "AlwaysKeepDownload field has wrong value")
|
||||
assert.False(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert.Equal(t, settings.PluginRepositoryLastCheckDuration.Every, 60, "PluginRepositoryLastCheckDuration field has wrong value")
|
||||
assert.False(t, settings.DisablePluginShortNameRepository, "DisablePluginShortNameRepository field has wrong value")
|
||||
})
|
||||
}
|
||||
|
||||
@ -64,44 +66,62 @@ func TestConfigMethods(t *testing.T) {
|
||||
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
|
||||
|
||||
config, err := LoadConfig()
|
||||
assert(t, err == nil, "Returned error when building config")
|
||||
assert.Nil(t, err, "Returned error when building config")
|
||||
|
||||
t.Run("Returns LegacyVersionFile from asdfrc file", func(t *testing.T) {
|
||||
legacyFile, err := config.LegacyVersionFile()
|
||||
assert(t, err == nil, "Returned error when loading settings")
|
||||
assert(t, legacyFile == true, "Expected LegacyVersionFile to be set")
|
||||
assert.Nil(t, err, "Returned error when loading settings")
|
||||
assert.True(t, legacyFile, "Expected LegacyVersionFile to be set")
|
||||
})
|
||||
|
||||
t.Run("Returns AlwaysKeepDownload from asdfrc file", func(t *testing.T) {
|
||||
alwaysKeepDownload, err := config.AlwaysKeepDownload()
|
||||
assert(t, err == nil, "Returned error when loading settings")
|
||||
assert(t, alwaysKeepDownload == true, "Expected AlwaysKeepDownload to be set")
|
||||
assert.Nil(t, err, "Returned error when loading settings")
|
||||
assert.True(t, alwaysKeepDownload, "Expected AlwaysKeepDownload to be set")
|
||||
})
|
||||
|
||||
t.Run("Returns PluginRepositoryLastCheckDuration from asdfrc file", func(t *testing.T) {
|
||||
checkDuration, err := config.PluginRepositoryLastCheckDuration()
|
||||
assert(t, err == nil, "Returned error when loading settings")
|
||||
assert(t, checkDuration.Never == true, "Expected PluginRepositoryLastCheckDuration to be set")
|
||||
assert(t, checkDuration.Every == 0, "Expected PluginRepositoryLastCheckDuration to be set")
|
||||
assert.Nil(t, err, "Returned error when loading settings")
|
||||
assert.True(t, checkDuration.Never, "Expected PluginRepositoryLastCheckDuration to be set")
|
||||
assert.Zero(t, checkDuration.Every, "Expected PluginRepositoryLastCheckDuration to be set")
|
||||
})
|
||||
|
||||
t.Run("Returns DisablePluginShortNameRepository from asdfrc file", func(t *testing.T) {
|
||||
DisablePluginShortNameRepository, err := config.DisablePluginShortNameRepository()
|
||||
assert(t, err == nil, "Returned error when loading settings")
|
||||
assert(t, DisablePluginShortNameRepository == true, "Expected DisablePluginShortNameRepository to be set")
|
||||
assert.Nil(t, err, "Returned error when loading settings")
|
||||
assert.True(t, DisablePluginShortNameRepository, "Expected DisablePluginShortNameRepository to be set")
|
||||
})
|
||||
}
|
||||
|
||||
func assert(t *testing.T, expr bool, message string) {
|
||||
t.Helper()
|
||||
func TestConfigGetHook(t *testing.T) {
|
||||
// Set the asdf config file location to the test file
|
||||
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
|
||||
|
||||
if !expr {
|
||||
t.Error(message)
|
||||
}
|
||||
}
|
||||
config, err := LoadConfig()
|
||||
assert.Nil(t, err, "Returned error when building config")
|
||||
|
||||
func refuteError(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatal("Returned unexpected error", err)
|
||||
}
|
||||
t.Run("Returns empty string when hook not present in asdfrc file", func(t *testing.T) {
|
||||
hookCmd, err := config.GetHook("post_asdf_plugin_add")
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, hookCmd)
|
||||
})
|
||||
|
||||
t.Run("Returns string containing Bash expression when present in asdfrc file", func(t *testing.T) {
|
||||
hookCmd, err := config.GetHook("pre_asdf_plugin_add")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, hookCmd, "echo Executing with args: $@")
|
||||
})
|
||||
|
||||
t.Run("Ignores trailing and leading spaces", func(t *testing.T) {
|
||||
hookCmd, err := config.GetHook("pre_asdf_plugin_add_test")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, hookCmd, "echo Executing with args: $@")
|
||||
})
|
||||
|
||||
t.Run("Preserves quoting", func(t *testing.T) {
|
||||
hookCmd, err := config.GetHook("pre_asdf_plugin_add_test2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, hookCmd, "echo 'Executing' \"with args: $@\"")
|
||||
})
|
||||
}
|
||||
|
5
config/testdata/asdfrc
vendored
5
config/testdata/asdfrc
vendored
@ -5,3 +5,8 @@ use_release_candidates = yes
|
||||
always_keep_download = yes
|
||||
plugin_repository_last_check_duration = never
|
||||
disable_plugin_short_name_repository = yes
|
||||
|
||||
# Hooks
|
||||
pre_asdf_plugin_add = echo Executing with args: $@
|
||||
pre_asdf_plugin_add_test = echo Executing with args: $@
|
||||
pre_asdf_plugin_add_test2 = echo 'Executing' "with args: $@"
|
||||
|
49
execute/execute.go
Normal file
49
execute/execute.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Package execute is a simple package that wraps the os/exec Command features
|
||||
// for convenient use in asdf. It was inspired by
|
||||
// https://github.com/chen-keinan/go-command-eval
|
||||
package execute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Command represents a Bash command that can be executed by asdf
|
||||
type Command struct {
|
||||
Command string
|
||||
Args []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
// New takes a string containing a Bash expression and a slice of string
|
||||
// arguments and returns a Command struct
|
||||
func New(command string, args []string) Command {
|
||||
return Command{Command: command, Args: args}
|
||||
}
|
||||
|
||||
// Run executes a Command with Bash and returns the error if there is one
|
||||
func (c Command) Run() error {
|
||||
args := append([]string{"-c", c.Command}, c.Args...)
|
||||
cmd := exec.Command("bash", args...)
|
||||
|
||||
cmd.Env = mapToSlice(c.Env)
|
||||
cmd.Stdin = c.Stdin
|
||||
|
||||
// Capture stdout and stderr
|
||||
cmd.Stdout = c.Stdout
|
||||
cmd.Stderr = c.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func mapToSlice(env map[string]string) (slice []string) {
|
||||
for key, value := range env {
|
||||
slice = append(slice, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
80
execute/execute_test.go
Normal file
80
execute/execute_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package execute
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Run("Returns new command", func(t *testing.T) {
|
||||
cmd := New("echo", []string{"test string"})
|
||||
assert.Equal(t, "echo", cmd.Command)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
t.Run("command is executed with bash", func(t *testing.T) {
|
||||
cmd := New("echo $(type -a sh)", []string{})
|
||||
|
||||
var stdout strings.Builder
|
||||
cmd.Stdout = &stdout
|
||||
err := cmd.Run()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "sh is /bin/sh\n", stdout.String())
|
||||
})
|
||||
|
||||
t.Run("positional args are passed to command", func(t *testing.T) {
|
||||
cmd := New("echo $0", []string{"test string"})
|
||||
|
||||
var stdout strings.Builder
|
||||
cmd.Stdout = &stdout
|
||||
err := cmd.Run()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "test string\n", stdout.String())
|
||||
})
|
||||
|
||||
t.Run("environment variables are passed to command", func(t *testing.T) {
|
||||
cmd := New("echo $MYVAR", []string{})
|
||||
cmd.Env = map[string]string{"MYVAR": "my var value"}
|
||||
|
||||
var stdout strings.Builder
|
||||
cmd.Stdout = &stdout
|
||||
err := cmd.Run()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "my var value\n", stdout.String())
|
||||
})
|
||||
|
||||
t.Run("captures stdout and stdin", func(t *testing.T) {
|
||||
cmd := New("echo 'a test' | tee /dev/stderr", []string{})
|
||||
cmd.Env = map[string]string{"MYVAR": "my var value"}
|
||||
|
||||
var stdout strings.Builder
|
||||
cmd.Stdout = &stdout
|
||||
var stderr strings.Builder
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "a test\n", stdout.String())
|
||||
assert.Equal(t, "a test\n", stderr.String())
|
||||
})
|
||||
|
||||
t.Run("returns error when non-zero exit code", func(t *testing.T) {
|
||||
cmd := New("exit 12", []string{})
|
||||
|
||||
var stdout strings.Builder
|
||||
cmd.Stdout = &stdout
|
||||
err := cmd.Run()
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", stdout.String())
|
||||
assert.Equal(t, 12, err.(*exec.ExitError).ExitCode())
|
||||
})
|
||||
}
|
29
hook/hook.go
Normal file
29
hook/hook.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Package hook provides a simple interface for running hook commands that may
|
||||
// be defined in the asdfrc file
|
||||
package hook
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"asdf/config"
|
||||
"asdf/execute"
|
||||
)
|
||||
|
||||
// Run gets a hook command from config and runs it with the provided arguments
|
||||
func Run(config config.Config, hookName string, arguments []string) error {
|
||||
hookCmd, err := config.GetHook(hookName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hookCmd == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := execute.New(hookCmd, arguments)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
39
hook/hook_test.go
Normal file
39
hook/hook_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"asdf/config"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
// Set the asdf config file location to the test file
|
||||
t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc")
|
||||
|
||||
t.Run("accepts config, hook name, and a slice of string arguments", func(t *testing.T) {
|
||||
config, err := config.LoadConfig()
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = Run(config, "pre_asdf_plugin_add_test", []string{})
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("passes argument to command", func(t *testing.T) {
|
||||
config, err := config.LoadConfig()
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = Run(config, "pre_asdf_plugin_add_test2", []string{"123"})
|
||||
assert.Equal(t, 123, err.(*exec.ExitError).ExitCode())
|
||||
})
|
||||
|
||||
t.Run("does not return error when no such hook is defined in asdfrc", func(t *testing.T) {
|
||||
config, err := config.LoadConfig()
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = Run(config, "nonexistant-hook", []string{})
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
7
hook/testdata/asdfrc
vendored
Normal file
7
hook/testdata/asdfrc
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# This is a test asdfrc file containing all possible values. Each field to set
|
||||
# to a value that is different than the default.
|
||||
|
||||
# Hooks
|
||||
pre_asdf_plugin_add = echo Executing with args: $@
|
||||
pre_asdf_plugin_add_test = echo Executing with args: $@
|
||||
pre_asdf_plugin_add_test2 = exit $0
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"asdf/config"
|
||||
"asdf/git"
|
||||
"asdf/hook"
|
||||
"asdf/pluginindex"
|
||||
)
|
||||
|
||||
@ -146,7 +147,20 @@ func Add(config config.Config, pluginName, pluginURL string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return git.NewRepo(pluginDir).Clone(pluginURL)
|
||||
// Run pre hooks
|
||||
hook.Run(config, "pre_asdf_plugin_add", []string{})
|
||||
hook.Run(config, fmt.Sprintf("pre_asdf_plugin_add_%s", pluginName), []string{})
|
||||
|
||||
err = git.NewRepo(pluginDir).Clone(pluginURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run post hooks
|
||||
hook.Run(config, "post_asdf_plugin_add", []string{})
|
||||
hook.Run(config, fmt.Sprintf("post_asdf_plugin_add_%s", pluginName), []string{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove uninstalls a plugin by removing it from the file system if installed
|
||||
|
Loading…
Reference in New Issue
Block a user