mirror of
https://github.com/asdf-vm/asdf.git
synced 2024-12-20 02:15:12 -07:00
Merge pull request #36 from asdf-vm/tb/plugin-update-cmd
feat(golang-rewrite): create plugin update command
This commit is contained in:
commit
0aba948c65
54
cmd/cmd.go
54
cmd/cmd.go
@ -82,9 +82,15 @@ func Execute() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Action: func(_ *cli.Context) error {
|
Flags: []cli.Flag{
|
||||||
log.Print("Ipsum")
|
&cli.BoolFlag{
|
||||||
return nil
|
Name: "all",
|
||||||
|
Usage: "Update all installed plugins",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *cli.Context) error {
|
||||||
|
args := cCtx.Args()
|
||||||
|
return pluginUpdateCommand(cCtx, logger, args.Get(0), args.Get(1))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -170,3 +176,45 @@ func pluginListCommand(cCtx *cli.Context, logger *log.Logger) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pluginUpdateCommand(cCtx *cli.Context, logger *log.Logger, pluginName, ref string) error {
|
||||||
|
updateAll := cCtx.Bool("all")
|
||||||
|
if !updateAll && pluginName == "" {
|
||||||
|
return cli.Exit("usage: asdf plugin-update {<name> [git-ref] | --all}", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("error loading config: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateAll {
|
||||||
|
installedPlugins, err := plugins.List(conf, false, false)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to get plugin list: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, plugin := range installedPlugins {
|
||||||
|
updatedToRef, err := plugins.Update(conf, plugin.Name, "")
|
||||||
|
formatUpdateResult(logger, plugin.Name, updatedToRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedToRef, err := plugins.Update(conf, pluginName, ref)
|
||||||
|
formatUpdateResult(logger, pluginName, updatedToRef, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUpdateResult(logger *log.Logger, pluginName, updatedToRef string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to update %s due to error: %s\n", pluginName, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Printf("updated %s to ref %s\n", pluginName, updatedToRef)
|
||||||
|
}
|
||||||
|
@ -9,8 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"asdf/config"
|
"asdf/config"
|
||||||
|
"asdf/plugins/git"
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,7 +18,9 @@ const (
|
|||||||
pluginAlreadyExists = "plugin named %q already added"
|
pluginAlreadyExists = "plugin named %q already added"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Plugin represents a plugin to the packages in asdf
|
// Plugin struct represents an asdf plugin to all asdf code. The name and dir
|
||||||
|
// fields are the most used fields. Ref and Dir only still git info, which is
|
||||||
|
// only information and shown to the user at times.
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
Name string
|
Name string
|
||||||
Dir string
|
Dir string
|
||||||
@ -42,25 +43,22 @@ func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) {
|
|||||||
var url string
|
var url string
|
||||||
var refString string
|
var refString string
|
||||||
location := filepath.Join(pluginsDir, file.Name())
|
location := filepath.Join(pluginsDir, file.Name())
|
||||||
repo, err := git.PlainOpen(location)
|
plugin := git.NewPlugin(location)
|
||||||
|
|
||||||
// TODO: Improve these error messages
|
// TODO: Improve these error messages
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins, err
|
return plugins, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if refs {
|
if refs {
|
||||||
ref, err := repo.Head()
|
refString, err = plugin.Head()
|
||||||
refString = ref.Hash().String()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins, err
|
return plugins, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if urls {
|
if urls {
|
||||||
remotes, err := repo.Remotes()
|
url, err = plugin.RemoteURL()
|
||||||
url = remotes[0].Config().URLs[0]
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins, err
|
return plugins, err
|
||||||
}
|
}
|
||||||
@ -107,17 +105,10 @@ func Add(config config.Config, pluginName, pluginURL string) error {
|
|||||||
return fmt.Errorf("unable to create plugin directory: %w", err)
|
return fmt.Errorf("unable to create plugin directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = git.PlainClone(pluginDir, false, &git.CloneOptions{
|
return git.NewPlugin(pluginDir).Clone(pluginURL)
|
||||||
URL: pluginURL,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to clone plugin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes a plugin with the provided name if installed
|
// Remove uninstalls a plugin by removing it from the file system if installed
|
||||||
func Remove(config config.Config, pluginName string) error {
|
func Remove(config config.Config, pluginName string) error {
|
||||||
err := validatePluginName(pluginName)
|
err := validatePluginName(pluginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,11 +129,33 @@ func Remove(config config.Config, pluginName string) error {
|
|||||||
return os.RemoveAll(pluginDir)
|
return os.RemoveAll(pluginDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update a plugin to a specific ref, or if no ref provided update to latest
|
||||||
|
func Update(config config.Config, pluginName, ref string) (string, error) {
|
||||||
|
exists, err := PluginExists(config.DataDir, pluginName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to check if plugin exists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("no such plugin: %s", pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginDir := PluginDirectory(config.DataDir, pluginName)
|
||||||
|
|
||||||
|
plugin := git.NewPlugin(pluginDir)
|
||||||
|
|
||||||
|
return plugin.Update(ref)
|
||||||
|
}
|
||||||
|
|
||||||
// PluginExists returns a boolean indicating whether or not a plugin with the
|
// PluginExists returns a boolean indicating whether or not a plugin with the
|
||||||
// provided name is currently installed
|
// provided name is currently installed
|
||||||
func PluginExists(dataDir, pluginName string) (bool, error) {
|
func PluginExists(dataDir, pluginName string) (bool, error) {
|
||||||
pluginDir := PluginDirectory(dataDir, pluginName)
|
pluginDir := PluginDirectory(dataDir, pluginName)
|
||||||
fileInfo, err := os.Stat(pluginDir)
|
return directoryExists(pluginDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func directoryExists(dir string) (bool, error) {
|
||||||
|
fileInfo, err := os.Stat(dir)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -154,13 +167,14 @@ func PluginExists(dataDir, pluginName string) (bool, error) {
|
|||||||
return fileInfo.IsDir(), nil
|
return fileInfo.IsDir(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginDirectory returns the directory a plugin would be installed in, if it
|
// PluginDirectory returns the directory a plugin with a given name would be in
|
||||||
// is installed
|
// if it were installed
|
||||||
func PluginDirectory(dataDir, pluginName string) string {
|
func PluginDirectory(dataDir, pluginName string) string {
|
||||||
return filepath.Join(DataDirectory(dataDir), pluginName)
|
return filepath.Join(DataDirectory(dataDir), pluginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataDirectory return the plugin directory inside the data directory
|
// DataDirectory returns the path to the plugin directory inside the data
|
||||||
|
// directory
|
||||||
func DataDirectory(dataDir string) string {
|
func DataDirectory(dataDir string) string {
|
||||||
return filepath.Join(dataDir, dataDirPlugins)
|
return filepath.Join(dataDir, dataDirPlugins)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,6 @@ func TestRemove(t *testing.T) {
|
|||||||
t.Run("returns error when invalid plugin name is given", func(t *testing.T) {
|
t.Run("returns error when invalid plugin name is given", func(t *testing.T) {
|
||||||
err := Remove(conf, "foo/bar/baz")
|
err := Remove(conf, "foo/bar/baz")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'"
|
expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'"
|
||||||
assert.ErrorContains(t, err, expectedErrMsg)
|
assert.ErrorContains(t, err, expectedErrMsg)
|
||||||
})
|
})
|
||||||
@ -173,6 +172,72 @@ func TestRemove(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
testDataDir := t.TempDir()
|
||||||
|
conf := config.Config{DataDir: testDataDir}
|
||||||
|
|
||||||
|
err := Add(conf, testPluginName, testRepo)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
badPluginName := "badplugin"
|
||||||
|
badRepo := PluginDirectory(testDataDir, badPluginName)
|
||||||
|
err = os.MkdirAll(badRepo, 0o777)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
givenConf config.Config
|
||||||
|
givenName string
|
||||||
|
givenRef string
|
||||||
|
wantSomeRef bool
|
||||||
|
wantErrMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "returns error when plugin with name does not exist",
|
||||||
|
givenConf: conf,
|
||||||
|
givenName: "nonexistant",
|
||||||
|
givenRef: "",
|
||||||
|
wantSomeRef: false,
|
||||||
|
wantErrMsg: "no such plugin: nonexistant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "returns error when plugin repo does not exist",
|
||||||
|
givenConf: conf,
|
||||||
|
givenName: "badplugin",
|
||||||
|
givenRef: "",
|
||||||
|
wantSomeRef: false,
|
||||||
|
wantErrMsg: "unable to open plugin Git repository: repository does not exist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "updates plugin when plugin with name exists",
|
||||||
|
givenConf: conf,
|
||||||
|
givenName: testPluginName,
|
||||||
|
givenRef: "",
|
||||||
|
wantSomeRef: true,
|
||||||
|
wantErrMsg: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
updatedToRef, err := Update(tt.givenConf, tt.givenName, tt.givenRef)
|
||||||
|
|
||||||
|
if tt.wantErrMsg == "" {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.ErrorContains(t, err, tt.wantErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantSomeRef == true {
|
||||||
|
assert.NotZero(t, updatedToRef)
|
||||||
|
} else {
|
||||||
|
assert.Zero(t, updatedToRef)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPluginExists(t *testing.T) {
|
func TestPluginExists(t *testing.T) {
|
||||||
testDataDir := t.TempDir()
|
testDataDir := t.TempDir()
|
||||||
pluginDir := PluginDirectory(testDataDir, testPluginName)
|
pluginDir := PluginDirectory(testDataDir, testPluginName)
|
||||||
@ -305,29 +370,34 @@ func installMockPluginRepo(dataDir, name string) (string, error) {
|
|||||||
return location, err
|
return location, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin\"", name))
|
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin init\"", name))
|
||||||
return location, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to make running commands easier
|
|
||||||
func runCmd(cmdName string, args ...string) error {
|
|
||||||
cmd := exec.Command(cmdName, args...)
|
|
||||||
|
|
||||||
// Capture stdout and stderr
|
|
||||||
var stdout strings.Builder
|
|
||||||
var stderr strings.Builder
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If command fails print both stderr and stdout
|
return location, err
|
||||||
fmt.Println("stdout:", stdout.String())
|
|
||||||
fmt.Println("stderr:", stderr.String())
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
err = runCmd("touch", filepath.Join(location, "README.md"))
|
||||||
|
if err != nil {
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runCmd("git", "-C", location, "add", "-A")
|
||||||
|
if err != nil {
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin readme \"", name))
|
||||||
|
if err != nil {
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// kind of ugly but I want a remote with a valid path so I use the same
|
||||||
|
// location as the remote. Probably should refactor
|
||||||
|
err = runCmd("git", "-C", location, "remote", "add", "origin", location)
|
||||||
|
if err != nil {
|
||||||
|
return location, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return location, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func moduleRoot() (string, error) {
|
func moduleRoot() (string, error) {
|
||||||
@ -360,3 +430,24 @@ func findModuleRoot(dir string) (roots string) {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function to make running commands easier
|
||||||
|
func runCmd(cmdName string, args ...string) error {
|
||||||
|
cmd := exec.Command(cmdName, args...)
|
||||||
|
|
||||||
|
// Capture stdout and stderr
|
||||||
|
var stdout strings.Builder
|
||||||
|
var stderr strings.Builder
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
// If command fails print both stderr and stdout
|
||||||
|
fmt.Println("stdout:", stdout.String())
|
||||||
|
fmt.Println("stderr:", stderr.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user