зеркало из https://github.com/Azure/draft-classic.git
feat(*): add plugin update cmd
This commit is contained in:
Родитель
d46fdf44cf
Коммит
577b00f4ed
|
@ -12,7 +12,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
||||
helpers "github.com/Azure/draft/pkg/testing"
|
||||
"github.com/Azure/draft/pkg/testing/helpers"
|
||||
)
|
||||
|
||||
const gitkeepfile = ".gitkeep"
|
||||
|
|
|
@ -29,6 +29,7 @@ func newPluginCmd(out io.Writer) *cobra.Command {
|
|||
newPluginInstallCmd(out),
|
||||
newPluginListCmd(out),
|
||||
newPluginRemoveCmd(out),
|
||||
newPluginUpdateCmd(out),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
||||
helpers "github.com/Azure/draft/pkg/testing"
|
||||
"github.com/Azure/draft/pkg/testing/helpers"
|
||||
)
|
||||
|
||||
func TestPluginRemoveCmd(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/helm/pkg/plugin"
|
||||
|
||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
||||
"github.com/Azure/draft/pkg/plugin/installer"
|
||||
)
|
||||
|
||||
type pluginUpdateCmd struct {
|
||||
names []string
|
||||
home draftpath.Home
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func newPluginUpdateCmd(out io.Writer) *cobra.Command {
|
||||
pcmd := &pluginUpdateCmd{out: out}
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <plugin>...",
|
||||
Short: "update one or more Draft plugins",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return pcmd.complete(args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return pcmd.run()
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (pcmd *pluginUpdateCmd) complete(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("please provide plugin name to update")
|
||||
}
|
||||
pcmd.names = args
|
||||
pcmd.home = draftpath.Home(homePath())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pcmd *pluginUpdateCmd) run() error {
|
||||
installer.Debug = flagDebug
|
||||
pluginsDir := pluginDirPath(pcmd.home)
|
||||
debug("loading installed plugins from %s", pluginsDir)
|
||||
plugins, err := findPlugins(pluginsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errorPlugins []string
|
||||
|
||||
for _, name := range pcmd.names {
|
||||
if found := findPlugin(plugins, name); found != nil {
|
||||
if err := updatePlugin(found, pcmd.home); err != nil {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err))
|
||||
} else {
|
||||
fmt.Fprintf(pcmd.out, "Updated plugin: %s\n", name)
|
||||
}
|
||||
} else {
|
||||
errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name))
|
||||
}
|
||||
}
|
||||
if len(errorPlugins) > 0 {
|
||||
return fmt.Errorf(strings.Join(errorPlugins, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePlugin(p *plugin.Plugin, home draftpath.Home) error {
|
||||
exactLocation, err := filepath.EvalSymlinks(p.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
absExactLocation, err := filepath.Abs(exactLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i, err := installer.FindSource(absExactLocation, home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := installer.Update(i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
debug("loading plugin from %s", i.Path())
|
||||
updatedPlugin, err := plugin.LoadDir(i.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runHook(updatedPlugin, plugin.Update)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
||||
)
|
||||
|
||||
func TestPluginUpdateCmd(t *testing.T) {
|
||||
// move this to e2e test suite soon
|
||||
target, err := newTestPluginEnv("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old, err := setupTestPluginEnv(target)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer teardownTestPluginEnv(target, old)
|
||||
|
||||
home := draftpath.Home(draftHome)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
update := &pluginUpdateCmd{
|
||||
names: []string{"server"},
|
||||
home: home,
|
||||
out: buf,
|
||||
}
|
||||
|
||||
if err := update.run(); err == nil {
|
||||
t.Errorf("expected plugin update to err but did not")
|
||||
}
|
||||
|
||||
install := &pluginInstallCmd{
|
||||
source: "https://github.com/michelleN/draft-server",
|
||||
version: "0.1.0",
|
||||
home: home,
|
||||
out: buf,
|
||||
}
|
||||
|
||||
if err := install.run(); err != nil {
|
||||
t.Fatalf("Erroring installing plugin")
|
||||
}
|
||||
|
||||
if err := update.run(); err != nil {
|
||||
t.Errorf("Erroring updating plugin: %v", err)
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,8 @@ type Installer interface {
|
|||
Install() error
|
||||
// Path is the directory of the installed plugin.
|
||||
Path() string
|
||||
// Update updates a plugin to $DRAFT_HOME.
|
||||
Update() error
|
||||
}
|
||||
|
||||
// Install installs a plugin to $DRAFT_HOME
|
||||
|
@ -40,6 +42,24 @@ func Install(i Installer) error {
|
|||
return i.Install()
|
||||
}
|
||||
|
||||
// Update updates a plugin in $DRAFT_HOME.
|
||||
func Update(i Installer) error {
|
||||
if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
|
||||
return errors.New("plugin does not exist")
|
||||
}
|
||||
|
||||
return i.Update()
|
||||
}
|
||||
|
||||
// FindSource determines the correct Installer for the given source.
|
||||
func FindSource(location string, home draftpath.Home) (Installer, error) {
|
||||
installer, err := existingVCSRepo(location, home)
|
||||
if err != nil && err.Error() == "Cannot detect VCS" {
|
||||
return installer, errors.New("cannot get information about plugin source")
|
||||
}
|
||||
return installer, err
|
||||
}
|
||||
|
||||
// New determines and returns the correct Installer for the given source
|
||||
func New(source, version string, home draftpath.Home) (Installer, error) {
|
||||
if isLocalReference(source) {
|
||||
|
|
|
@ -34,3 +34,9 @@ func (i *LocalInstaller) Install() error {
|
|||
|
||||
return i.link(src)
|
||||
}
|
||||
|
||||
// Update updates a local repository
|
||||
func (i *LocalInstaller) Update() error {
|
||||
debug("local repository is auto-updated")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
@ -31,6 +32,7 @@ func NewVCSInstaller(source, version string, home draftpath.Home) (*VCSInstaller
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := &VCSInstaller{
|
||||
Repo: repo,
|
||||
Version: version,
|
||||
|
@ -63,6 +65,34 @@ func (i *VCSInstaller) Install() error {
|
|||
return i.link(i.Repo.LocalPath())
|
||||
}
|
||||
|
||||
// Update updates a remote repository
|
||||
func (i *VCSInstaller) Update() error {
|
||||
debug("updating %s", i.Repo.Remote())
|
||||
if i.Repo.IsDirty() {
|
||||
return errors.New("plugin repo was modified")
|
||||
}
|
||||
if err := i.Repo.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isPlugin(i.Repo.LocalPath()) {
|
||||
return ErrMissingMetadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingVCSRepo(location string, home draftpath.Home) (Installer, error) {
|
||||
repo, err := vcs.NewRepo("", location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &VCSInstaller{
|
||||
Repo: repo,
|
||||
base: newBase(repo.Remote(), home),
|
||||
}
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// Filter a list of versions to only included semantic versions. The response
|
||||
// is a mapping of the original version to the semantic version.
|
||||
func getSemVers(refs []string) []*semver.Version {
|
||||
|
|
|
@ -82,3 +82,66 @@ func TestVCSInstallerSuccess(t *testing.T) {
|
|||
t.Errorf("expected error for plugin exists, got (%v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVCSInstallerUpdate(t *testing.T) {
|
||||
|
||||
dh, err := ioutil.TempDir("", "draft-home-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dh)
|
||||
|
||||
home := draftpath.Home(dh)
|
||||
if err := os.MkdirAll(home.Plugins(), 0755); err != nil {
|
||||
t.Fatalf("Could not create %s: %s", home.Plugins(), err)
|
||||
}
|
||||
|
||||
source := "https://github.com/michelleN/draft-server"
|
||||
i, err := New(source, "0.1.0", home)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// ensure a VCSInstaller was returned
|
||||
_, ok := i.(*VCSInstaller)
|
||||
if !ok {
|
||||
t.Error("expected a VCSInstaller")
|
||||
}
|
||||
|
||||
if err := Update(i); err == nil {
|
||||
t.Error("expected error for plugin does not exist, got none")
|
||||
} else if err.Error() != "plugin does not exist" {
|
||||
t.Errorf("expected error for plugin does not exist, got (%v)", err)
|
||||
}
|
||||
|
||||
// Install plugin before update
|
||||
if err := Install(i); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Test FindSource method for positive result
|
||||
pluginInfo, err := FindSource(i.Path(), home)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote()
|
||||
if repoRemote != source {
|
||||
t.Errorf("invalid source found, expected %q got %q", source, repoRemote)
|
||||
}
|
||||
|
||||
// Update plugin
|
||||
if err := Update(i); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Test update failure
|
||||
os.Remove(filepath.Join(i.Path(), "plugin.yaml"))
|
||||
// Testing update for error
|
||||
if err := Update(i); err == nil {
|
||||
t.Error("expected error for plugin modified, got none")
|
||||
} else if err.Error() != "plugin repo was modified" {
|
||||
t.Errorf("expected error for plugin modified, got (%v)", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package testing
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
// copyTree copies src directory content tree to dest.
|
||||
// CopyTree copies src directory content tree to dest.
|
||||
// If dest exists, it's deleted.
|
||||
// We don't handle symlinks (not needed in this test helper)
|
||||
func CopyTree(t *testing.T, src, dest string) {
|
Загрузка…
Ссылка в новой задаче