Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate receipt status on install/upgrade #555

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions cmd/krew/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ import (
"sigs.k8s.io/krew/cmd/krew/cmd/internal"
"sigs.k8s.io/krew/internal/index/indexscanner"
"sigs.k8s.io/krew/internal/installation"
"sigs.k8s.io/krew/internal/pathutil"
"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/index"
)

type pluginEntry struct {
p index.Plugin
indexName string
}

func init() {
var (
manifest, manifestURL, archiveFileOverride *string
Expand Down Expand Up @@ -90,45 +96,56 @@ Remarks:
return errors.New("--archive can be specified only with --manifest or --manifest-url")
}

var install []index.Plugin
var install []pluginEntry
for _, name := range pluginNames {
plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(constants.DefaultIndexName), name)
indexName, pluginName := pathutil.CanonicalPluginName(name)
plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName)
if err != nil {
if os.IsNotExist(err) {
return errors.Errorf("plugin %q does not exist in the plugin index", name)
}
return errors.Wrapf(err, "failed to load plugin %q from the index", name)
}
install = append(install, plugin)
install = append(install, pluginEntry{
p: plugin,
indexName: indexName,
})
}

if *manifest != "" {
plugin, err := indexscanner.ReadPluginFromFile(*manifest)
if err != nil {
return errors.Wrap(err, "failed to load plugin manifest from file")
}
install = append(install, plugin)
install = append(install, pluginEntry{
p: plugin,
indexName: constants.DefaultIndexName,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a case we did not think of.
I guess we can keep shoving them into default. thoughts @corneliusweig ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what happens at the moment. But we can use the opportunity to separate this better from the default index. So instead, what about setting the indexName to detached or something similar?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ @chriskim06 do you mind making a small PR?

})
} else if *manifestURL != "" {
plugin, err := readPluginFromURL(*manifestURL)
if err != nil {
return errors.Wrap(err, "failed to read plugin manifest file from url")
}
install = append(install, plugin)
install = append(install, pluginEntry{
p: plugin,
indexName: constants.DefaultIndexName,
})
}

if len(install) == 0 {
return cmd.Help()
}

for _, plugin := range install {
klog.V(2).Infof("Will install plugin: %s\n", plugin.Name)
for _, pluginEntry := range install {
klog.V(2).Infof("Will install plugin: %s/%s\n", pluginEntry.indexName, pluginEntry.p.Name)
}

var failed []string
var returnErr error
for _, plugin := range install {
for _, entry := range install {
plugin := entry.p
fmt.Fprintf(os.Stderr, "Installing plugin: %s\n", plugin.Name)
err := installation.Install(paths, plugin, installation.InstallOpts{
err := installation.Install(paths, plugin, entry.indexName, installation.InstallOpts{
ArchiveFileOverride: *archiveFileOverride,
})
if err == installation.ErrIsAlreadyInstalled {
Expand Down
7 changes: 4 additions & 3 deletions cmd/krew/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"sigs.k8s.io/krew/cmd/krew/cmd/internal"
"sigs.k8s.io/krew/internal/index/indexscanner"
"sigs.k8s.io/krew/internal/installation"
"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/internal/pathutil"
)

func init() {
Expand Down Expand Up @@ -63,7 +63,8 @@ kubectl krew upgrade foo bar"`,

var nErrors int
for _, name := range pluginNames {
plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(constants.DefaultIndexName), name)
indexName, pluginName := pathutil.CanonicalPluginName(name)
plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName)
if err != nil {
if !os.IsNotExist(err) {
return errors.Wrapf(err, "failed to load the plugin manifest for plugin %s", name)
Expand All @@ -74,7 +75,7 @@ kubectl krew upgrade foo bar"`,

if err == nil {
fmt.Fprintf(os.Stderr, "Upgrading plugin: %s\n", name)
err = installation.Upgrade(paths, plugin)
err = installation.Upgrade(paths, plugin, indexName)
if ignoreUpgraded && err == installation.ErrIsAlreadyUpgraded {
fmt.Fprintf(os.Stderr, "Skipping plugin %s, it is already on the newest version\n", name)
continue
Expand Down
27 changes: 27 additions & 0 deletions integration_test/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ func TestKrewInstall_StdinAndPositionalArguments(t *testing.T) {
test.AssertExecutableNotInPATH("kubectl-" + validPlugin2)
}

func TestKrewInstall_ExplicitDefaultIndex(t *testing.T) {
skipShort(t)

test, cleanup := NewTest(t)
defer cleanup()

test.Krew("install", "default/"+validPlugin).RunOrFail()
test.AssertExecutableInPATH("kubectl-" + validPlugin)
}

func TestKrewInstall_CustomIndex(t *testing.T) {
skipShort(t)

test, cleanup := NewTest(t)
defer cleanup()

test.WithEnv(constants.EnableMultiIndexSwitch, 1).WithIndex()
test.Krew("index", "add", "foo", test.TempDir().Path("index/"+constants.DefaultIndexName)).RunOrFail()
test.Krew("install", "foo/"+validPlugin).RunOrFail()
test.AssertExecutableInPATH("kubectl-" + validPlugin)

if err := test.Krew("install", "invalid/"+validPlugin2).Run(); err == nil {
t.Fatal("expected install from invalid index to fail")
}
test.AssertExecutableNotInPATH("kubectl-" + validPlugin2)
}

func TestKrewInstall_Manifest(t *testing.T) {
skipShort(t)

Expand Down
10 changes: 8 additions & 2 deletions internal/installation/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type installOperation struct {
binDir string
}

// PluginEntry describes a plugin and the index it comes from.
type PluginEntry struct {
Plugin index.Plugin
IndexName string
}

// Plugin lifecycle errors
var (
ErrIsAlreadyInstalled = errors.New("can't install, the newest version is already installed")
Expand All @@ -54,7 +60,7 @@ var (

// Install will download and install a plugin. The operation tries
// to not get the plugin dir in a bad state if it fails during the process.
func Install(p environment.Paths, plugin index.Plugin, opts InstallOpts) error {
func Install(p environment.Paths, plugin index.Plugin, indexName string, opts InstallOpts) error {
klog.V(2).Infof("Looking for installed versions")
_, err := receipt.Load(p.PluginInstallReceiptPath(plugin.Name))
if err == nil {
Expand Down Expand Up @@ -85,7 +91,7 @@ func Install(p environment.Paths, plugin index.Plugin, opts InstallOpts) error {
return errors.Wrap(err, "install failed")
}
klog.V(3).Infof("Storing install receipt for plugin %s", plugin.Name)
err = receipt.Store(plugin, p.PluginInstallReceiptPath(plugin.Name))
err = receipt.Store(receipt.New(plugin, indexName), p.PluginInstallReceiptPath(plugin.Name))
return errors.Wrap(err, "installation receipt could not be stored, uninstall may fail")
}

Expand Down
18 changes: 15 additions & 3 deletions internal/installation/receipt/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import (
"sigs.k8s.io/krew/pkg/index"
)

// Store saves the given plugin receipt at the destination.
// Store saves the given receipt at the destination.
// The caller has to ensure that the destination directory exists.
func Store(plugin index.Plugin, dest string) error {
yamlBytes, err := yaml.Marshal(plugin)
func Store(receipt index.Receipt, dest string) error {
yamlBytes, err := yaml.Marshal(receipt)
if err != nil {
return errors.Wrapf(err, "convert to yaml")
}
Expand All @@ -41,3 +41,15 @@ func Store(plugin index.Plugin, dest string) error {
func Load(path string) (index.Receipt, error) {
return indexscanner.ReadReceiptFromFile(path)
}

// New returns a new receipt with the given plugin and index name.
func New(plugin index.Plugin, indexName string) index.Receipt {
return index.Receipt{
Plugin: plugin,
Status: index.ReceiptStatus{
Source: index.SourceIndex{
Name: indexName,
},
},
}
}
17 changes: 14 additions & 3 deletions internal/installation/receipt/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"sigs.k8s.io/krew/internal/index/indexscanner"
"sigs.k8s.io/krew/internal/testutil"
"sigs.k8s.io/krew/pkg/constants"
)

func TestStore(t *testing.T) {
Expand All @@ -32,7 +33,7 @@ func TestStore(t *testing.T) {
testReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V()
dest := tmpDir.Path("some-plugin.yaml")

if err := Store(testPlugin, dest); err != nil {
if err := Store(testReceipt, dest); err != nil {
t.Fatal(err)
}

Expand All @@ -51,15 +52,15 @@ func TestLoad(t *testing.T) {
defer cleanup()

testPlugin := testutil.NewPlugin().WithName("foo").WithPlatforms(testutil.NewPlatform().V()).V()
if err := Store(testPlugin, tmpDir.Path("foo.yaml")); err != nil {
testPluginReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V()
if err := Store(testPluginReceipt, tmpDir.Path("foo.yaml")); err != nil {
t.Fatal(err)
}

gotPlugin, err := Load(tmpDir.Path("foo.yaml"))
if err != nil {
t.Fatal(err)
}
testPluginReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V()
if diff := cmp.Diff(&gotPlugin, &testPluginReceipt); diff != "" {
t.Fatal(diff)
}
Expand All @@ -71,3 +72,13 @@ func TestLoad_preservesNonExistsError(t *testing.T) {
t.Fatalf("returned error is not ENOENT: %+v", err)
}
}

func TestNew(t *testing.T) {
testPlugin := testutil.NewPlugin().WithName("foo").WithPlatforms(testutil.NewPlatform().V()).V()
wantReceipt := testutil.NewReceipt().WithPlugin(testPlugin).V()

gotReceipt := New(testPlugin, constants.DefaultIndexName)
if diff := cmp.Diff(gotReceipt, wantReceipt); diff != "" {
t.Fatalf("expected receipts to match: %s", diff)
}
}
4 changes: 2 additions & 2 deletions internal/installation/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

// Upgrade will reinstall and delete the old plugin. The operation tries
// to not get the plugin dir in a bad state if it fails during the process.
func Upgrade(p environment.Paths, plugin index.Plugin) error {
func Upgrade(p environment.Paths, plugin index.Plugin, indexName string) error {
installReceipt, err := receipt.Load(p.PluginInstallReceiptPath(plugin.Name))
if err != nil {
return errors.Wrapf(err, "failed to load install receipt for plugin %q", plugin.Name)
Expand Down Expand Up @@ -78,7 +78,7 @@ func Upgrade(p environment.Paths, plugin index.Plugin) error {
}

klog.V(2).Infof("Upgrading install receipt for plugin %s", plugin.Name)
if err = receipt.Store(plugin, p.PluginInstallReceiptPath(plugin.Name)); err != nil {
if err = receipt.Store(receipt.New(plugin, indexName), p.PluginInstallReceiptPath(plugin.Name)); err != nil {
return errors.Wrap(err, "installation receipt could not be stored, uninstall may fail")
}

Expand Down