зеркало из 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"
|
"testing"
|
||||||
|
|
||||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
"github.com/Azure/draft/pkg/draft/draftpath"
|
||||||
helpers "github.com/Azure/draft/pkg/testing"
|
"github.com/Azure/draft/pkg/testing/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const gitkeepfile = ".gitkeep"
|
const gitkeepfile = ".gitkeep"
|
||||||
|
|
|
@ -29,6 +29,7 @@ func newPluginCmd(out io.Writer) *cobra.Command {
|
||||||
newPluginInstallCmd(out),
|
newPluginInstallCmd(out),
|
||||||
newPluginListCmd(out),
|
newPluginListCmd(out),
|
||||||
newPluginRemoveCmd(out),
|
newPluginRemoveCmd(out),
|
||||||
|
newPluginUpdateCmd(out),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Azure/draft/pkg/draft/draftpath"
|
"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) {
|
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
|
Install() error
|
||||||
// Path is the directory of the installed plugin.
|
// Path is the directory of the installed plugin.
|
||||||
Path() string
|
Path() string
|
||||||
|
// Update updates a plugin to $DRAFT_HOME.
|
||||||
|
Update() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install installs a plugin to $DRAFT_HOME
|
// Install installs a plugin to $DRAFT_HOME
|
||||||
|
@ -40,6 +42,24 @@ func Install(i Installer) error {
|
||||||
return i.Install()
|
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
|
// New determines and returns the correct Installer for the given source
|
||||||
func New(source, version string, home draftpath.Home) (Installer, error) {
|
func New(source, version string, home draftpath.Home) (Installer, error) {
|
||||||
if isLocalReference(source) {
|
if isLocalReference(source) {
|
||||||
|
|
|
@ -34,3 +34,9 @@ func (i *LocalInstaller) Install() error {
|
||||||
|
|
||||||
return i.link(src)
|
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
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -31,6 +32,7 @@ func NewVCSInstaller(source, version string, home draftpath.Home) (*VCSInstaller
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
i := &VCSInstaller{
|
i := &VCSInstaller{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Version: version,
|
Version: version,
|
||||||
|
@ -63,6 +65,34 @@ func (i *VCSInstaller) Install() error {
|
||||||
return i.link(i.Repo.LocalPath())
|
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
|
// Filter a list of versions to only included semantic versions. The response
|
||||||
// is a mapping of the original version to the semantic version.
|
// is a mapping of the original version to the semantic version.
|
||||||
func getSemVers(refs []string) []*semver.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)
|
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 (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// copyTree copies src directory content tree to dest.
|
// CopyTree copies src directory content tree to dest.
|
||||||
// If dest exists, it's deleted.
|
// If dest exists, it's deleted.
|
||||||
// We don't handle symlinks (not needed in this test helper)
|
// We don't handle symlinks (not needed in this test helper)
|
||||||
func CopyTree(t *testing.T, src, dest string) {
|
func CopyTree(t *testing.T, src, dest string) {
|
Загрузка…
Ссылка в новой задаче