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

Adds --manifest-url flag to allow plugin installation from remote manifests #350

Closed
Closed
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
20 changes: 14 additions & 6 deletions cmd/krew/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import (

func init() {
var (
manifest, archiveFileOverride *string
noUpdateIndex *bool
manifest, manifestURL, archiveFileOverride *string
noUpdateIndex *bool
)

// installCmd represents the install command
Expand Down Expand Up @@ -62,6 +62,10 @@ Remarks:
var pluginNames = make([]string, len(args))
copy(pluginNames, args)

if *manifest != "" && *manifestURL != "" {
rewanthtammana marked this conversation as resolved.
Show resolved Hide resolved
return errors.New("--manifest cannot be specified with --manifest-url")
}

if !isTerminal(os.Stdin) && (len(pluginNames) != 0 || *manifest != "") {
fmt.Fprintln(os.Stderr, "WARNING: Detected stdin, but discarding it because of --manifest or args")
}
Expand All @@ -81,8 +85,8 @@ Remarks:
return errors.New("must specify either specify either plugin names (via positional arguments or STDIN), or --manifest; not both")
Copy link
Member

Choose a reason for hiding this comment

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

you missed this part. this ought to also factor in --manifest-url.

}

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

var install []index.Plugin
Expand All @@ -94,11 +98,14 @@ Remarks:
install = append(install, plugin)
}

if *manifest != "" {
plugin, err := indexscanner.ReadPluginFile(*manifest)
var plugin index.Plugin
if *manifest != "" || *manifestURL != "" {
var err error
plugin, err = internal.GetPlugin(*manifest, *manifestURL)
if err != nil {
return errors.Wrap(err, "failed to load custom manifest file")
}

if err := validation.ValidatePlugin(plugin.Name, plugin); err != nil {
rewanthtammana marked this conversation as resolved.
Show resolved Hide resolved
return errors.Wrap(err, "plugin manifest validation error")
}
Expand Down Expand Up @@ -153,6 +160,7 @@ Remarks:
}

manifest = installCmd.Flags().String("manifest", "", "(Development-only) specify plugin manifest directly.")
manifestURL = installCmd.Flags().String("manifest-url", "", "(Development-only) specify plugin manifest URL directly.")
archiveFileOverride = installCmd.Flags().String("archive", "", "(Development-only) force all downloads to use the specified file")
noUpdateIndex = installCmd.Flags().Bool("no-update-index", false, "(Experimental) do not update local copy of plugin index before installing")

Expand Down
49 changes: 49 additions & 0 deletions cmd/krew/cmd/internal/read_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2019 The Kubernetes Authors.
Copy link
Member

Choose a reason for hiding this comment

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

this cmd/ package does nearly nothing noteworthy that requires creating a new package for. especially because cmd/ directory directly has "tool" names (krew, generate-plugin-list, ...) this doesn't fit here.

//
// 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 internal

import (
"net/http"

"github.com/pkg/errors"

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

// readRemotePluginManifest returns the plugin for a remote manifest
func readRemotePluginManifest(url string) (index.Plugin, error) {
resp, err := http.Get(url)
if err != nil {
return index.Plugin{}, errors.Wrapf(err, "Error downloading manifest from %q", url)
}
defer resp.Body.Close()

plugin, err := indexscanner.DecodePluginFile(resp.Body)
if err != nil {
return index.Plugin{}, errors.Wrapf(err, "Error decoding manifest from %q", url)
}

return plugin, nil
}
Comment on lines +27 to +40
Copy link
Member

Choose a reason for hiding this comment

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

this doesn't have unit tests :)
would be good to unit-test this, rather than trying to test /empty.yaml /invalid.yaml etc.


// GetPlugin is an abstraction layer for manifest and manifest-url handlers and returns plugin object
func GetPlugin(manifest string, manifestURL string) (index.Plugin, error) {
rewanthtammana marked this conversation as resolved.
Show resolved Hide resolved
if manifest != "" {
return indexscanner.ReadPluginFile(manifest)
}

return readRemotePluginManifest(manifestURL)
}
Comment on lines +42 to +49
Copy link
Member

Choose a reason for hiding this comment

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

This isn't really a good way to do things.
We could do this at the call site, now you just embedded the decisionmaking here, and it doesn't have unit tests.

115 changes: 114 additions & 1 deletion integration_test/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package integrationtest

import (
"net"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
Expand All @@ -28,6 +31,7 @@ const (

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

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

Expand Down Expand Up @@ -105,6 +109,115 @@ func TestKrewInstall_Manifest(t *testing.T) {
test.AssertExecutableInPATH("kubectl-" + validPlugin)
}

func startLocalServer(t *testing.T) (*httptest.Server, string) {
t.Helper()

server := httptest.NewUnstartedServer(http.FileServer(http.Dir("testdata")))

listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("trouble starting local server")
}
server.Listener = listener
server.Start()
return server, server.URL
Comment on lines +113 to +123
Copy link
Member

Choose a reason for hiding this comment

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

You can probably just do this:

httptest.NewServer(..)
return server.URL, server.Close

it does everything you want, and then you can embed it to the test because it's 2 lines.

}

func TestKrewInstall_ManifestURL(t *testing.T) {
rewanthtammana marked this conversation as resolved.
Show resolved Hide resolved
skipShort(t)

server, baseURL := startLocalServer(t)
defer server.Close()

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

test.Krew("install",
"--manifest-url", baseURL+manifestURI).RunOrFail()
test.AssertExecutableInPATH("kubectl-" + validPlugin)
}

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

server, baseURL := startLocalServer(t)
defer server.Close()

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

test.Krew("install",
"--manifest-url", baseURL+archiveManifestURI,
"--archive", filepath.Join("testdata", fooPlugin+".tar.gz")).
RunOrFail()
test.AssertExecutableInPATH("kubectl-" + fooPlugin)
}

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

server, baseURL := startLocalServer(t)
defer server.Close()

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

err := test.Krew("install",
"--manifest-url", baseURL+manifestURI,
"--manifest", filepath.Join("testdata", fooPlugin+constants.ManifestExtension)).Run()
if err == nil {
t.Errorf("expected install to fail but was successful")
}
}

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

server, baseURL := startLocalServer(t)
defer server.Close()

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

err := test.Krew("install",
"--manifest-url", baseURL+emtpyManifestURI).Run()
if err == nil {
t.Errorf("expected install to fail but was successful")
}
}
Comment on lines +173 to +187
Copy link
Member

Choose a reason for hiding this comment

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

what does this really test?
our validation method? aren't those already covered with unit tests?


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

server, baseURL := startLocalServer(t)
defer server.Close()

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

err := test.Krew("install",
"--manifest-url", baseURL+invalidManifestURI).Run()
if err == nil {
t.Errorf("expected install to fail but was successful")
}
}
Comment on lines +189 to +203
Copy link
Member

Choose a reason for hiding this comment

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

what does this really test?
our validation method? aren't those already covered with unit tests?


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

server, baseURL := startLocalServer(t)
defer server.Close()

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

err := test.Krew("install",
"--manifest-url", baseURL+notExistManifestURI).Run()
if err == nil {
t.Errorf("expected install to fail but was successful")
}
}

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

Expand All @@ -128,7 +241,7 @@ func TestKrewInstall_OnlyArchive(t *testing.T) {
"--archive", filepath.Join("testdata", fooPlugin+".tar.gz")).
Run()
if err == nil {
t.Errorf("Expected install to fail but was successful")
t.Errorf("expected install to fail but was successful")
}
}

Expand Down
Empty file.
2 changes: 2 additions & 0 deletions integration_test/testdata/invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind; Plugin
5 changes: 5 additions & 0 deletions integration_test/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const (
krewBinaryEnv = "KREW_BINARY"
validPlugin = "konfig" // a plugin in central index with small size
validPlugin2 = "mtail" // a plugin in central index with small size
manifestURI = "/konfig.yaml"
emtpyManifestURI = "/empty.yaml"
invalidManifestURI = "/invalid.yaml"
notExistManifestURI = "/doesnotexist.yaml"
archiveManifestURI = "/foo.yaml"
)

var (
Expand Down