Skip to content

Commit

Permalink
Save install receipt (#195)
Browse files Browse the repository at this point in the history
* 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 1831b1b
Show file tree
Hide file tree
Showing 41 changed files with 4,223 additions and 25 deletions.
14 changes: 14 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions cmd/krew/cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/krew/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/krew/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/krew/cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/krew/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
28 changes: 23 additions & 5 deletions pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/environment/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/index/indexscanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions pkg/index/indexscanner/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
},
},
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
44 changes: 44 additions & 0 deletions pkg/info/info.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 1831b1b

Please sign in to comment.