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

Allow mocking of url downloads with local file (--archive) #67

Merged
merged 1 commit into from
Oct 11, 2018
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
47 changes: 21 additions & 26 deletions cmd/krew/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import (
"bufio"
"fmt"
"os"
"path/filepath"

"github.com/GoogleContainerTools/krew/pkg/index"
"github.com/GoogleContainerTools/krew/pkg/index/indexscanner"
"github.com/GoogleContainerTools/krew/pkg/installation"

"github.com/GoogleContainerTools/krew/pkg/index"
"github.com/golang/glog"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
Expand All @@ -32,7 +31,7 @@ import (

func init() {
var forceHEAD *bool
var manifest *string
var manifest, forceDownloadFile *string
ahmetb marked this conversation as resolved.
Show resolved Hide resolved

// installCmd represents the install command
installCmd := &cobra.Command{
Expand All @@ -45,7 +44,7 @@ All plugins will be downloaded and made available to: "kubectl plugin <name>"`,
copy(pluginNames, args)

if (len(pluginNames) != 0 || *manifest != "") && !(isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())) {
fmt.Fprintln(os.Stderr, "Detected Stdin, but discarding it because of --source or args")
fmt.Fprintln(os.Stderr, "Detected Stdin, but discarding it because of --manifest or args")
}

if len(pluginNames) == 0 && *manifest == "" && !(isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())) {
Expand All @@ -60,7 +59,11 @@ All plugins will be downloaded and made available to: "kubectl plugin <name>"`,
}

if len(pluginNames) != 0 && *manifest != "" {
return errors.New("must specify either specify stdin or source or args")
return errors.New("must specify either specify stdin or --manifest or args")
}

if *forceDownloadFile != "" && *manifest == "" {
return errors.New("--archive can be specified only with --manifest")
}

var install []index.Plugin
Expand All @@ -73,22 +76,24 @@ All plugins will be downloaded and made available to: "kubectl plugin <name>"`,
}

if *manifest != "" {
file, err := getFileFromArg(*manifest)
if err != nil {
return errors.Wrapf(err, "failed to get the file %q", *manifest)
}
plugin, err := indexscanner.ReadPluginFile(file)
// TODO(ahmetb) do not clone index (ensureIndex) in PreRunE when
// custom manifest is specified (krew initial installation
// scenario).
plugin, err := indexscanner.ReadPluginFile(*manifest)
if err != nil {
return err
return errors.Wrap(err, "failed to load custom manifest file")
}
if err := plugin.Validate(plugin.Name); err != nil {
return errors.Wrap(err, "failed to validate the plugin file")
return errors.Wrap(err, "plugin manifest validation error")
}
install = append(install, plugin)
}

if len(install) > 1 && *forceHEAD {
return errors.New("can't use HEAD option with multiple plugins")
return errors.New("can't use --HEAD option with multiple plugins")
}
if len(install) > 1 && *manifest != "" {
return errors.New("can't use --manifest option with multiple plugins")
}

if len(install) == 0 {
Expand All @@ -104,7 +109,7 @@ All plugins will be downloaded and made available to: "kubectl plugin <name>"`,
// Do install
for _, plugin := range install {
glog.V(2).Infof("Installing plugin: %s\n", plugin.Name)
err := installation.Install(paths, plugin, *forceHEAD)
err := installation.Install(paths, plugin, *forceHEAD, *forceDownloadFile)
if err == installation.ErrIsAlreadyInstalled {
glog.Warningf("Skipping plugin %s, it is already installed", plugin.Name)
continue
Expand All @@ -128,18 +133,8 @@ All plugins will be downloaded and made available to: "kubectl plugin <name>"`,
}

forceHEAD = installCmd.Flags().Bool("HEAD", false, "Force HEAD if versioned and HEAD installs are possible.")
manifest = installCmd.Flags().String("source", "", "(Development-only) specify plugin manifest directly.")
manifest = installCmd.Flags().String("manifest", "", "(Development-only) specify plugin manifest directly.")
forceDownloadFile = installCmd.Flags().String("archive", "", "(Development-only) force all downloads to use the specified file")

rootCmd.AddCommand(installCmd)
}

func getFileFromArg(file string) (string, error) {
if filepath.IsAbs(file) {
return file, nil
}
abs, err := filepath.Abs(filepath.Join(os.Getenv("PWD"), file))
if err != nil {
return "", errors.Wrapf(err, "failed to find absolute file path")
}
return abs, nil
}
29 changes: 21 additions & 8 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,19 +236,30 @@ The checksum of the archive file specified on `head` won't be verified.
...
```

### Running the Plugin
### Running the Plugin Locally

To test the plugin locally, you can install the plugin with:
To test the plugin locally before uploading it to the [Main Index], you can
install it locally by providing the manifest file:

```bash
kubectl plugin install -v=4 --source=./foo.yaml
kubectl krew install -v=4 --manifest=./foo.yaml
```

If you did not make the plugin archive available on the `uri` yet, you can use
a local file in the `--archive` flag while specifying a custom manifest. This
will ignore the URL specified in the `uri:` and use the local file (but will
still use `sha256` field to do integrity check):

```bash
kubectl krew install --manifest=./foo.yaml --archive=./foo.zip
```

This will install the `foo` plugin.
To see the plugin directory, get the `InstallPath`:

To see the plugin directory, find the `InstallPath`:

```bash
kubectl plugin krew version
kubectl krew version
```

The installation target directory for the `foo` plugin is
Expand All @@ -258,17 +269,17 @@ There should always be only one version directory.
You can now run your plugin!

```bash
kubectl plugin foo
kubectl foo
```

### Cleaning up

After you have tested the plugin, remove it with `kubectl plugin remove foo`.
After you have tested the plugin, remove it with `kubectl krew remove foo`.

### Publishing the Plugin

After you have tested that the plugin can be installed and works you should
create a pull request for the [Main Index](https://github.com/GoogleContainerTools/krew-index).
create a pull request for the [Main Index][index].
After the pull request gets accepted into the main index, the plugin will be available for
all users.

Expand All @@ -281,3 +292,5 @@ The new plugin file should be submitted to the `plugins/` directory in the index
Create a pull request with the updated `uri` and `sha256`,
it is also useful to change the `version` field so that users can distinguish
the different versions.

[index]: https://github.com/GoogleContainerTools/krew-index
10 changes: 10 additions & 0 deletions pkg/download/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package download
import (
"io"
"net/http"
"os"
)

// Fetcher is used to get files from a URI.
Expand All @@ -36,3 +37,12 @@ func (HTTPFetcher) Get(uri string) (io.ReadCloser, error) {
}
return resp.Body, nil
}

type fileFetcher struct{ f string }

func (f fileFetcher) Get(_ string) (io.ReadCloser, error) {
return os.Open(f.f)
}

// NewFileFetcher returns a local file reader.
func NewFileFetcher(path string) Fetcher { return fileFetcher{f: path} }
23 changes: 14 additions & 9 deletions pkg/installation/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,35 @@ const (
krewPluginName = "krew"
)

func downloadAndMove(version, uri string, fos []index.FileOperation, downloadPath, installPath string) (dst string, err error) {
func downloadAndMove(version, uri string, fos []index.FileOperation, downloadPath, installPath, forceDownloadFile string) (dst string, err error) {
glog.V(3).Infof("Creating download dir %q", downloadPath)
if err = os.MkdirAll(downloadPath, 0755); err != nil {
return "", errors.Wrapf(err, "could not create download path %q", downloadPath)
}
defer os.RemoveAll(downloadPath)

var fetcher download.Fetcher = download.HTTPFetcher{}
if forceDownloadFile != "" {
fetcher = download.NewFileFetcher(forceDownloadFile)
}

if version == headVersion {
glog.V(1).Infof("Getting latest version from HEAD")
err = download.GetInsecure(uri, downloadPath, download.HTTPFetcher{})
glog.V(1).Infof("Getting latest version from HEAD without sha256 verification")
err = download.GetInsecure(uri, downloadPath, fetcher)
} else {
glog.V(1).Infof("Getting sha256 (%s) signed version", version)
err = download.GetWithSha256(uri, downloadPath, version, download.HTTPFetcher{})
err = download.GetWithSha256(uri, downloadPath, version, fetcher)
}
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to download and verify file")
}

return moveToInstallDir(downloadPath, installPath, version, fos)
}

// 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, forceHEAD bool) error {
func Install(p environment.Paths, plugin index.Plugin, forceHEAD bool, forceDownloadFile string) error {
glog.V(2).Infof("Looking for installed versions")
_, ok, err := findInstalledPluginVersion(p.InstallPath(), p.BinPath(), plugin.Name)
if err != nil {
Expand All @@ -80,11 +85,11 @@ func Install(p environment.Paths, plugin index.Plugin, forceHEAD bool) error {
if err != nil {
return err
}
return install(plugin.Name, version, uri, bin, p, fos)
return install(plugin.Name, version, uri, bin, p, fos, forceDownloadFile)
}

func install(plugin, version, uri, bin string, p environment.Paths, fos []index.FileOperation) error {
dst, err := downloadAndMove(version, uri, fos, filepath.Join(p.DownloadPath(), plugin), p.PluginInstallPath(plugin))
func install(plugin, version, uri, bin string, p environment.Paths, fos []index.FileOperation, forceDownloadFile string) error {
dst, err := downloadAndMove(version, uri, fos, filepath.Join(p.DownloadPath(), plugin), p.PluginInstallPath(plugin), forceDownloadFile)
if err != nil {
return errors.Wrap(err, "failed to dowload and move during installation")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/installation/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func Upgrade(p environment.Paths, plugin index.Plugin, currentKrewVersion string

// Re-Install
glog.V(1).Infof("Installing new version %s", newVersion)
if err := install(plugin.Name, newVersion, uri, binName, p, fos); err != nil {
if err := install(plugin.Name, newVersion, uri, binName, p, fos, ""); err != nil {
return errors.Wrap(err, "failed to install new version")
}

Expand Down