Skip to content
Permalink
Browse files

Save install receipt (#195)

* Add helpers for paths of plugin receipts

* Add google/go-cmp for expressive diffs in test assertions

* Remove plugin receipt on uninstall

* Save plugin manifest in receipt directory

* Load plugin manifest from receipts directory or the index

* Do not expect specific folder layout in LoadPluginFileFromFS

* Fix godoc for environment paths

* Review comments

* Swap plugin install and saving receipts

Saving receipts is less prone to failure than the whole plugin
installation. When swapped, krew will have an inconsistent state in
fewer occasions.

* Add test to ensure LoadManifestFromReceiptOrIndex returns ENOENT error when plugin does not exist

* Put receipts in folder 'receipts' instead of 'receipts/krew-index'

* Fix linter issues

* Rename ReceiptsPath -> InstallReceiptPath

* Save the new install receipt when upgrading a plugin
  • Loading branch information...
corneliusweig authored and k8s-ci-robot committed Jul 2, 2019
1 parent 82a267d commit 1831b1be9e801b83a75714ce449361a5e46f86a8
Showing with 4,223 additions and 25 deletions.
  1. +14 βˆ’0 Gopkg.lock
  2. +4 βˆ’0 Gopkg.toml
  3. +2 βˆ’2 cmd/krew/cmd/info.go
  4. +1 βˆ’1 cmd/krew/cmd/install.go
  5. +2 βˆ’1 cmd/krew/cmd/root.go
  6. +1 βˆ’1 cmd/krew/cmd/search.go
  7. +1 βˆ’1 cmd/krew/cmd/upgrade.go
  8. +23 βˆ’5 pkg/environment/environment.go
  9. +9 βˆ’0 pkg/environment/environment_test.go
  10. +4 βˆ’4 pkg/index/indexscanner/scanner.go
  11. +4 βˆ’4 pkg/index/indexscanner/scanner_test.go
  12. +44 βˆ’0 pkg/info/info.go
  13. +158 βˆ’0 pkg/info/info_test.go
  14. +25 βˆ’3 pkg/installation/install.go
  15. +6 βˆ’0 pkg/installation/upgrade.go
  16. +36 βˆ’0 pkg/receipt/receipt.go
  17. +73 βˆ’0 pkg/receipt/receipt_test.go
  18. +4 βˆ’3 pkg/testutil/tempdir.go
  19. +27 βˆ’0 vendor/github.com/google/go-cmp/LICENSE
  20. +616 βˆ’0 vendor/github.com/google/go-cmp/cmp/compare.go
  21. +15 βˆ’0 vendor/github.com/google/go-cmp/cmp/export_panic.go
  22. +23 βˆ’0 vendor/github.com/google/go-cmp/cmp/export_unsafe.go
  23. +17 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
  24. +122 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
  25. +372 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
  26. +9 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
  27. +10 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go
  28. +10 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go
  29. +99 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/function/func.go
  30. +23 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
  31. +26 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
  32. +104 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
  33. +45 βˆ’0 vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
  34. +524 βˆ’0 vendor/github.com/google/go-cmp/cmp/options.go
  35. +308 βˆ’0 vendor/github.com/google/go-cmp/cmp/path.go
  36. +51 βˆ’0 vendor/github.com/google/go-cmp/cmp/report.go
  37. +296 βˆ’0 vendor/github.com/google/go-cmp/cmp/report_compare.go
  38. +279 βˆ’0 vendor/github.com/google/go-cmp/cmp/report_reflect.go
  39. +333 βˆ’0 vendor/github.com/google/go-cmp/cmp/report_slices.go
  40. +382 βˆ’0 vendor/github.com/google/go-cmp/cmp/report_text.go
  41. +121 βˆ’0 vendor/github.com/google/go-cmp/cmp/report_value.go

Some generated files are not rendered by default. Learn more.

@@ -46,3 +46,7 @@
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

[[constraint]]
name = "github.com/google/go-cmp"
version = "0.3.0"
@@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"sigs.k8s.io/krew/pkg/index"
"sigs.k8s.io/krew/pkg/index/indexscanner"
"sigs.k8s.io/krew/pkg/info"
)

// infoCmd represents the info command
@@ -41,7 +41,7 @@ available version, platform availability and the caveats.
Example:
kubectl krew info PLUGIN`,
RunE: func(cmd *cobra.Command, args []string) error {
plugin, err := indexscanner.LoadPluginFileFromFS(paths.IndexPath(), args[0])
plugin, err := info.LoadManifestFromReceiptOrIndex(paths, args[0])
if os.IsNotExist(err) {
return errors.Errorf("plugin %q not found", args[0])
} else if err != nil {
@@ -84,7 +84,7 @@ Remarks:

var install []index.Plugin
for _, name := range pluginNames {
plugin, err := indexscanner.LoadPluginFileFromFS(paths.IndexPath(), name)
plugin, err := indexscanner.LoadPluginFileFromFS(paths.IndexPluginsPath(), name)
if err != nil {
return errors.Wrapf(err, "failed to load plugin %q from the index", name)
}
@@ -68,7 +68,8 @@ func init() {
if err := ensureDirs(paths.BasePath(),
paths.DownloadPath(),
paths.InstallPath(),
paths.BinPath()); err != nil {
paths.BinPath(),
paths.InstallReceiptPath()); err != nil {
glog.Fatal(err)
}
}
@@ -41,7 +41,7 @@ Examples:
To fuzzy search plugins with a keyword:
kubectl krew search KEYWORD`,
RunE: func(cmd *cobra.Command, args []string) error {
plugins, err := indexscanner.LoadPluginListFromFS(paths.IndexPath())
plugins, err := indexscanner.LoadPluginListFromFS(paths.IndexPluginsPath())
if err != nil {
return errors.Wrap(err, "failed to load the list of plugins from the index")
}
@@ -53,7 +53,7 @@ kubectl krew upgrade foo bar"`,
}

for _, name := range pluginNames {
plugin, err := indexscanner.LoadPluginFileFromFS(paths.IndexPath(), name)
plugin, err := indexscanner.LoadPluginFileFromFS(paths.IndexPluginsPath(), name)
if err != nil {
return errors.Wrapf(err, "failed to load the index file for plugin %s", plugin.Name)
}
@@ -22,6 +22,7 @@ import (
"github.com/pkg/errors"
"k8s.io/client-go/util/homedir"

"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/pathutil"
)

@@ -56,13 +57,23 @@ func (p Paths) BasePath() string { return p.base }

// IndexPath returns the base directory where plugin index repository is cloned.
//
// e.g. {IndexPath}/plugins/{plugin}.yaml
// e.g. {BasePath}/index/
func (p Paths) IndexPath() string { return filepath.Join(p.base, "index") }

// IndexPluginsPath returns the plugins directory of the index repository.
//
// e.g. {BasePath}/index/plugins/
func (p Paths) IndexPluginsPath() string { return filepath.Join(p.base, "index", "plugins") }

// InstallReceiptPath returns the base directory where plugin receipts are stored.
//
// e.g. {BasePath}/receipts
func (p Paths) InstallReceiptPath() string { return filepath.Join(p.base, "receipts") }

// BinPath returns the path where plugin executable symbolic links are found.
// This path should be added to $PATH in client machine.
//
// e.g. {BinPath}/kubectl-foo
// e.g. {BasePath}/bin
func (p Paths) BinPath() string { return filepath.Join(p.base, "bin") }

// DownloadPath returns a temporary directory for downloading plugins. It does
@@ -71,20 +82,27 @@ func (p Paths) DownloadPath() string { return filepath.Join(p.tmp, "krew-downloa

// InstallPath returns the base directory for plugin installations.
//
// e.g. {InstallPath}/{plugin-name}
// e.g. {BasePath}/store
func (p Paths) InstallPath() string { return filepath.Join(p.base, "store") }

// PluginInstallPath returns the path to install the plugin.
//
// e.g. {PluginInstallPath}/{version}/{..files..}
// e.g. {InstallPath}/{version}/{..files..}
func (p Paths) PluginInstallPath(plugin string) string {
return filepath.Join(p.InstallPath(), plugin)
}

// PluginReceiptPath returns the path to the install receipt for plugin.
//
// e.g. {InstallReceiptPath}/{plugin}.yaml
func (p Paths) PluginReceiptPath(plugin string) string {
return filepath.Join(p.InstallReceiptPath(), plugin+constants.ManifestExtension)
}

// PluginVersionInstallPath returns the path to the specified version of specified
// plugin.
//
// e.g. {PluginVersionInstallPath} = {PluginInstallPath}/{version}
// e.g. {PluginInstallPath}/{plugin}/{version}
func (p Paths) PluginVersionInstallPath(plugin, version string) string {
return filepath.Join(p.InstallPath(), plugin, version)
}
@@ -57,6 +57,9 @@ func TestPaths(t *testing.T) {
if got, expected := p.IndexPath(), filepath.FromSlash("/foo/index"); got != expected {
t.Fatalf("IndexPath()=%s; expected=%s", got, expected)
}
if got, expected := p.IndexPluginsPath(), filepath.FromSlash("/foo/index/plugins"); got != expected {
t.Fatalf("IndexPluginsPath()=%s; expected=%s", got, expected)
}
if got, expected := p.InstallPath(), filepath.FromSlash("/foo/store"); got != expected {
t.Fatalf("InstallPath()=%s; expected=%s", got, expected)
}
@@ -69,6 +72,12 @@ func TestPaths(t *testing.T) {
if got := p.DownloadPath(); !strings.HasSuffix(got, "krew-downloads") {
t.Fatalf("DownloadPath()=%s; expected suffix 'krew-downloads'", got)
}
if got := p.InstallReceiptPath(); !strings.HasSuffix(got, filepath.FromSlash("receipts")) {
t.Fatalf("InstallReceiptPath()=%s; expected suffix 'receipts'", got)
}
if got := p.PluginReceiptPath("my-plugin"); !strings.HasSuffix(got, filepath.FromSlash("receipts/my-plugin.yaml")) {
t.Fatalf("PluginReceiptPath()=%s; expected suffix 'receipts/my-plugin.yaml'", got)
}
}

func TestGetExecutedVersion(t *testing.T) {
@@ -38,7 +38,7 @@ func LoadPluginListFromFS(indexDir string) ([]index.Plugin, error) {
return nil, err
}

files, err := ioutil.ReadDir(filepath.Join(indexDir, "plugins"))
files, err := ioutil.ReadDir(indexDir)
if err != nil {
return nil, errors.Wrap(err, "failed to open index dir")
}
@@ -65,17 +65,17 @@ func LoadPluginListFromFS(indexDir string) ([]index.Plugin, error) {

// LoadPluginFileFromFS loads a plugins index file by its name. When plugin
// file not found, it returns an error that can be checked with os.IsNotExist.
func LoadPluginFileFromFS(indexDir, pluginName string) (index.Plugin, error) {
func LoadPluginFileFromFS(pluginsDir, pluginName string) (index.Plugin, error) {
if !index.IsSafePluginName(pluginName) {
return index.Plugin{}, errors.Errorf("plugin name %q not allowed", pluginName)
}

glog.V(4).Infof("Reading plugin %q", pluginName)
indexDir, err := filepath.EvalSymlinks(filepath.Join(indexDir, "plugins"))
pluginsDir, err := filepath.EvalSymlinks(pluginsDir)
if err != nil {
return index.Plugin{}, err
}
p, err := ReadPluginFile(filepath.Join(indexDir, pluginName+constants.ManifestExtension))
p, err := ReadPluginFile(filepath.Join(pluginsDir, pluginName+constants.ManifestExtension))
if os.IsNotExist(err) {
return index.Plugin{}, err
} else if err != nil {
@@ -101,7 +101,7 @@ func TestLoadIndexListFromFS(t *testing.T) {
{
name: "load index dir",
args: args{
indexDir: filepath.Join(testdataPath(t), "testindex"),
indexDir: filepath.Join(testdataPath(t), "testindex", "plugins"),
},
},
}
@@ -134,7 +134,7 @@ func TestLoadIndexFileFromFS(t *testing.T) {
{
name: "load single index file",
args: args{
indexDir: filepath.Join(testdataPath(t), "testindex"),
indexDir: filepath.Join(testdataPath(t), "testindex", "plugins"),
pluginName: "foo",
},
wantErr: false,
@@ -143,7 +143,7 @@ func TestLoadIndexFileFromFS(t *testing.T) {
{
name: "plugin file not found",
args: args{
indexDir: filepath.FromSlash("./testdata"),
indexDir: filepath.FromSlash("./testdata/plugins"),
pluginName: "not",
},
wantErr: true,
@@ -152,7 +152,7 @@ func TestLoadIndexFileFromFS(t *testing.T) {
{
name: "plugin file bad name",
args: args{
indexDir: filepath.FromSlash("./testdata"),
indexDir: filepath.FromSlash("./testdata/plugins"),
pluginName: "wrongname",
},
wantErr: true,
@@ -0,0 +1,44 @@
// Copyright 2019 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package info

import (
"os"

"github.com/golang/glog"
"github.com/pkg/errors"

"sigs.k8s.io/krew/pkg/environment"
"sigs.k8s.io/krew/pkg/index"
"sigs.k8s.io/krew/pkg/index/indexscanner"
)

// LoadManifestFromReceiptOrIndex tries to load a plugin manifest from the
// receipts directory or from the index directory if the former fails.
func LoadManifestFromReceiptOrIndex(p environment.Paths, name string) (index.Plugin, error) {
receipt, err := indexscanner.LoadPluginFileFromFS(p.InstallReceiptPath(), name)

if err == nil {
glog.V(3).Infof("Found plugin manifest for %q in the receipts dir", name)
return receipt, nil
}

if !os.IsNotExist(err) {
return index.Plugin{}, errors.Wrapf(err, "loading plugin %q from receipts dir", name)
}

glog.V(3).Infof("Plugin manifest for %q not found in the receipts dir", name)
return indexscanner.LoadPluginFileFromFS(p.IndexPluginsPath(), name)
}

0 comments on commit 1831b1b

Please sign in to comment.
You can’t perform that action at this time.