feat(*): add plugin update cmd

This commit is contained in:
Michelle Noorali 2017-08-07 16:54:39 -04:00
Родитель d46fdf44cf
Коммит 577b00f4ed
10 изменённых файлов: 273 добавлений и 4 удалений

Просмотреть файл

@ -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) {