feat(plugin): add --version flag to plugin update command

Add support for specifying a version constraint when updating plugins,
matching the existing behavior of helm plugin install.

Changes:
- Add --version flag to plugin update command
- Update VCSInstaller.Update() to resolve and checkout specified version
- Update FindSource() to accept version parameter
- Add TestVCSInstallerUpdateWithVersion test for version support

Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com>
pull/31615/head
MrJack 10 months ago
parent ce13e3b658
commit 30352ca1ac

@ -157,8 +157,8 @@ func NewForSource(source, version string) (installer Installer, err error) {
} }
// FindSource determines the correct Installer for the given source. // FindSource determines the correct Installer for the given source.
func FindSource(location string) (Installer, error) { func FindSource(location string, version string) (Installer, error) {
installer, err := existingVCSRepo(location) installer, err := existingVCSRepo(location, version)
if err != nil && err.Error() == "Cannot detect VCS" { if err != nil && err.Error() == "Cannot detect VCS" {
slog.Warn( slog.Warn(
"cannot get information about plugin source", "cannot get information about plugin source",

@ -38,14 +38,15 @@ type VCSInstaller struct {
base base
} }
func existingVCSRepo(location string) (Installer, error) { func existingVCSRepo(location string, version string) (Installer, error) {
repo, err := vcs.NewRepo("", location) repo, err := vcs.NewRepo("", location)
if err != nil { if err != nil {
return nil, err return nil, err
} }
i := &VCSInstaller{ i := &VCSInstaller{
Repo: repo, Repo: repo,
base: newBase(repo.Remote()), Version: version,
base: newBase(repo.Remote()),
} }
return i, nil return i, nil
} }
@ -104,6 +105,17 @@ func (i *VCSInstaller) Update() error {
if err := i.Repo.Update(); err != nil { if err := i.Repo.Update(); err != nil {
return err return err
} }
ref, err := i.solveVersion(i.Repo)
if err != nil {
return err
}
if ref != "" {
if err := i.setVersion(i.Repo, ref); err != nil {
return err
}
}
if !isPlugin(i.Repo.LocalPath()) { if !isPlugin(i.Repo.LocalPath()) {
return ErrMissingMetadata return ErrMissingMetadata
} }

@ -49,6 +49,7 @@ func (r *testRepo) UpdateVersion(version string) error {
r.current = version r.current = version
return r.err return r.err
} }
func (r *testRepo) IsDirty() bool { return false }
func TestVCSInstaller(t *testing.T) { func TestVCSInstaller(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)
@ -81,7 +82,7 @@ func TestVCSInstaller(t *testing.T) {
require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists") require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists")
// Testing FindSource method, expect error because plugin code is not a cloned repository // Testing FindSource method, expect error because plugin code is not a cloned repository
_, err = FindSource(i.Path()) _, err = FindSource(i.Path(), "")
require.Error(t, err, "expected error for inability to find plugin source, got none") require.Error(t, err, "expected error for inability to find plugin source, got none")
require.EqualErrorf(t, err, "cannot get information about plugin source", "expected error for inability to find plugin source") require.EqualErrorf(t, err, "cannot get information about plugin source", "expected error for inability to find plugin source")
} }
@ -131,7 +132,7 @@ func TestVCSInstallerUpdate(t *testing.T) {
} }
// Test FindSource method for positive result // Test FindSource method for positive result
pluginInfo, err := FindSource(i.Path()) pluginInfo, err := FindSource(i.Path(), "")
require.NoError(t, err) require.NoError(t, err)
vcsInstaller := pluginInfo.(*VCSInstaller) vcsInstaller := pluginInfo.(*VCSInstaller)
@ -147,3 +148,61 @@ func TestVCSInstallerUpdate(t *testing.T) {
// Testing update for error // Testing update for error
require.EqualErrorf(t, Update(vcsInstaller), "plugin repo was modified", "expected error for plugin modified") require.EqualErrorf(t, Update(vcsInstaller), "plugin repo was modified", "expected error for plugin modified")
} }
func TestVCSInstallerUpdateWithVersion(t *testing.T) {
ensure.HelmHome(t)
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
}
source := "https://github.com/adamreese/helm-env"
testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo-v1")
repo := &testRepo{
local: testRepoPath,
remote: source,
tags: []string{"0.1.0", "0.1.1", "0.2.0"},
}
// First install without version
i, err := NewForSource(source, "")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
vcsInstaller, ok := i.(*VCSInstaller)
if !ok {
t.Fatal("expected a VCSInstaller")
}
vcsInstaller.Repo = repo
if err := Install(i); err != nil {
t.Fatal(err)
}
// Now test update with specific version constraint
vcsInstaller.Version = "~0.1.0"
if err := Update(vcsInstaller); err != nil {
t.Fatal(err)
}
if repo.current != "0.1.1" {
t.Fatalf("expected version '0.1.1', got %q", repo.current)
}
// Test update with different version constraint
vcsInstaller.Version = "0.2.0"
if err := Update(vcsInstaller); err != nil {
t.Fatal(err)
}
if repo.current != "0.2.0" {
t.Fatalf("expected version '0.2.0', got %q", repo.current)
}
// Test update with non-existent version
vcsInstaller.Version = "0.3.0"
if err := Update(vcsInstaller); err == nil {
t.Fatal("expected error for version does not exist, got none")
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", "0.3.0", source) {
t.Fatalf("expected error for version does not exist, got (%v)", err)
}
}

@ -29,7 +29,8 @@ import (
) )
type pluginUpdateOptions struct { type pluginUpdateOptions struct {
names []string names []string
version string
} }
func newPluginUpdateCmd(out io.Writer) *cobra.Command { func newPluginUpdateCmd(out io.Writer) *cobra.Command {
@ -49,6 +50,7 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
return o.run(out) return o.run(out)
}, },
} }
cmd.Flags().StringVar(&o.version, "version", "", "specify a version constraint. If this is not specified, the latest version is installed")
return cmd return cmd
} }
@ -70,8 +72,8 @@ func (o *pluginUpdateOptions) run(out io.Writer) error {
for _, name := range o.names { for _, name := range o.names {
if found := findPlugin(plugins, name); found != nil { if found := findPlugin(plugins, name); found != nil {
if err := updatePlugin(found); err != nil { if err := updatePlugin(found, o.version); err != nil {
errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%w)", name, err)) errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%v)", name, err))
} else { } else {
fmt.Fprintf(out, "Updated plugin: %s\n", name) fmt.Fprintf(out, "Updated plugin: %s\n", name)
} }
@ -85,7 +87,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error {
return nil return nil
} }
func updatePlugin(p plugin.Plugin) error { func updatePlugin(p plugin.Plugin, version string) error {
exactLocation, err := filepath.EvalSymlinks(p.Dir()) exactLocation, err := filepath.EvalSymlinks(p.Dir())
if err != nil { if err != nil {
return err return err
@ -95,7 +97,7 @@ func updatePlugin(p plugin.Plugin) error {
return err return err
} }
i, err := installer.FindSource(absExactLocation) i, err := installer.FindSource(absExactLocation, version)
if err != nil { if err != nil {
return err return err
} }

Loading…
Cancel
Save