Skip to content

Commit

Permalink
Merge pull request #36 from asdf-vm/tb/plugin-update-cmd
Browse files Browse the repository at this point in the history
feat(golang-rewrite): create plugin update command
  • Loading branch information
Stratus3D authored May 4, 2024
2 parents 4b00c9d + bcd893b commit f262ffa
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 44 deletions.
54 changes: 51 additions & 3 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,15 @@ func Execute() {
},
{
Name: "update",
Action: func(_ *cli.Context) error {
log.Print("Ipsum")
return nil
Flags: []cli.Flag{
&cli.BoolFlag{
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))
},
},
},
Expand Down Expand Up @@ -170,3 +176,45 @@ func pluginListCommand(cCtx *cli.Context, logger *log.Logger) error {

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)
}
60 changes: 37 additions & 23 deletions plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
"regexp"

"asdf/config"

"github.com/go-git/go-git/v5"
"asdf/plugins/git"
)

const (
Expand All @@ -19,7 +18,9 @@ const (
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 {
Name string
Dir string
Expand All @@ -42,25 +43,22 @@ func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) {
var url string
var refString string
location := filepath.Join(pluginsDir, file.Name())
repo, err := git.PlainOpen(location)
plugin := git.NewPlugin(location)

// TODO: Improve these error messages
if err != nil {
return plugins, err
}

if refs {
ref, err := repo.Head()
refString = ref.Hash().String()

refString, err = plugin.Head()
if err != nil {
return plugins, err
}
}

if urls {
remotes, err := repo.Remotes()
url = remotes[0].Config().URLs[0]

url, err = plugin.RemoteURL()
if err != nil {
return plugins, err
}
Expand Down Expand Up @@ -107,17 +105,10 @@ func Add(config config.Config, pluginName, pluginURL string) error {
return fmt.Errorf("unable to create plugin directory: %w", err)
}

_, err = git.PlainClone(pluginDir, false, &git.CloneOptions{
URL: pluginURL,
})
if err != nil {
return fmt.Errorf("unable to clone plugin: %w", err)
}

return nil
return git.NewPlugin(pluginDir).Clone(pluginURL)
}

// 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 {
err := validatePluginName(pluginName)
if err != nil {
Expand All @@ -138,11 +129,33 @@ func Remove(config config.Config, pluginName string) error {
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
// provided name is currently installed
func PluginExists(dataDir, pluginName string) (bool, error) {
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) {
return false, nil
}
Expand All @@ -154,13 +167,14 @@ func PluginExists(dataDir, pluginName string) (bool, error) {
return fileInfo.IsDir(), nil
}

// PluginDirectory returns the directory a plugin would be installed in, if it
// is installed
// PluginDirectory returns the directory a plugin with a given name would be in
// if it were installed
func PluginDirectory(dataDir, pluginName string) string {
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 {
return filepath.Join(dataDir, dataDirPlugins)
}
Expand Down
127 changes: 109 additions & 18 deletions plugins/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ func TestRemove(t *testing.T) {
t.Run("returns error when invalid plugin name is given", func(t *testing.T) {
err := Remove(conf, "foo/bar/baz")
assert.NotNil(t, err)

expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'"
assert.ErrorContains(t, err, expectedErrMsg)
})
Expand All @@ -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) {
testDataDir := t.TempDir()
pluginDir := PluginDirectory(testDataDir, testPluginName)
Expand Down Expand Up @@ -305,29 +370,34 @@ func installMockPluginRepo(dataDir, name string) (string, error) {
return location, err
}

err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin\"", name))
return location, err
}
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin init\"", name))
if err != nil {
return location, err
}

// helper function to make running commands easier
func runCmd(cmdName string, args ...string) error {
cmd := exec.Command(cmdName, args...)
err = runCmd("touch", filepath.Join(location, "README.md"))
if err != nil {
return location, err
}

// Capture stdout and stderr
var stdout strings.Builder
var stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = runCmd("git", "-C", location, "add", "-A")
if err != nil {
return location, err
}

err := cmd.Run()
err = runCmd("git", "-C", location, "commit", "-q", "-m", fmt.Sprintf("\"asdf %s plugin readme \"", name))
if err != nil {
// If command fails print both stderr and stdout
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())
return err
return location, err
}

return nil
// 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) {
Expand Down Expand Up @@ -360,3 +430,24 @@ func findModuleRoot(dir string) (roots string) {
}
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
}

0 comments on commit f262ffa

Please sign in to comment.