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

Conversation

rewanthtammana
Copy link

@rewanthtammana rewanthtammana commented Oct 3, 2019

This feature addition lets the user/developer to install a krew plugin directly from the source URL instead of manually downloading the plugin file.

Problem statement

Install kubectl plugin from config file

Current approach

wget https://raw.githubusercontent.com/rewanth1997/kubectl-fields/master/deploy/krew/plugin.yaml
kubectl krew install --manifest=./plugin.yaml

Proposed approach

kubectl krew install --manifest-url="https://github.com/rewanth1997/kubectl-fields/raw/master/deploy/krew/plugin.yaml"

This new feature addition will download the plugin from the URL to a temporary location and then installs the plugin from that temporary fie using the regular manifest method.

Please review the feature request. I'm happy to make the necessary changes if any.

Regards,
Rewanth

@k8s-ci-robot

This comment has been minimized.

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Oct 3, 2019
@k8s-ci-robot k8s-ci-robot requested review from ahmetb and soltysh October 3, 2019 14:04
@rewanthtammana rewanthtammana force-pushed the manifest-url branch 2 times, most recently from 51dadcc to 3da3c54 Compare October 3, 2019 18:13
@codecov-io

This comment has been minimized.

@rewanthtammana
Copy link
Author

/assign @soltysh

@ahmetb
Copy link
Member

ahmetb commented Oct 3, 2019

This proposal was previously discussed & denied, I believe.

@rewanthtammana
Copy link
Author

rewanthtammana commented Oct 4, 2019

Oh, I couldn't find any such discussion on Github.

This PR downloads the yaml file from the URL and stores it in a temporary location. After download, it transfers the control to --manifest flag. After installation, this PR cleans up the file from the system.

I couldn't think of any potential drawbacks. In fact, I see this as an advantage for developers/users. May I know the reason behind denying this idea?

@ahmetb
Copy link
Member

ahmetb commented Oct 4, 2019

I also tried to find but couldn't. Now I'm thinking if it was a private discussion with @corneliusweig.

The main reasoning was a about a few points:

  1. You can just do --manifest <(curl url) and compose tools/pipes/sub-shells as one should do with UNIX command-line tools.

  2. --manifest-url is yet another thing to maintain, and makes the user/test surface larger. We thought about detecting url in --manifest but that is also "magic", which is not a good thing to do in tools.

Since this has came up multiple times by now, we might be open to it.

/unassign @soltysh
/assign
/assign @corneliusweig

@k8s-ci-robot k8s-ci-robot assigned ahmetb and corneliusweig and unassigned soltysh Oct 4, 2019
@corneliusweig
Copy link
Contributor

Hi @rewanth1997, the issue @ahmetb mentioned is #193.


I think the main reason against the --manifest-url option is that this is only intended for plugin devs and not for krew users. Adding this --manifest-url option would also encourage end-users to install dangling plugins via this option. However, we don't want this, because krew should also take care of updates and this doesn't work when installing from URL.

However, even plugin devs do stumble over this --manifest <(curl URL) thing. How about this: if a user tries to install with --manifest URL, we could detect if the argument is in fact a URL and print an error suggesting to try --manifest <(curl URL) instead?

@rewanthtammana
Copy link
Author

rewanthtammana commented Oct 9, 2019

I wasn't aware of the update issue. Thanks for mentioning that.

I just checked the update module implementation in krew, https://github.com/kubernetes-sigs/krew/blob/master/pkg/gitutil/git.go#L59. With this implementation, the plugins installed via kubectl install --manifest <(curl ...) won't be updated with krew update command.

The idea of throwing a custom error to use curl seems good but building a generic regex expression for URL is very tough. Limiting this just to http/https protocol check isn't a good idea either.

@corneliusweig
Copy link
Contributor

@rewanth1997 Implementing the URL parser is not necessary. Go already has that built in. See https://github.com/kubernetes-sigs/krew/pull/302/files

if _, err := url.ParseRequestURI(word); err == nil {
   return true
}

@rewanthtammana
Copy link
Author

rewanthtammana commented Oct 13, 2019

@corneliusweig I don't think this will work for our use case.

url.ParseRequestURI considers anything starting with "/" as URI. It might be an absolute path to the plugin file in the host machine.

"/home/rewanth/kubectl-fields/deploy/krew/plugin.yaml" gets detected as URI but we are looking for URL.

package main

import (
    "fmt"
    "net/url"
)

func main() {
    fmt.Println(isUrl("https://github.com/kubernetes-sigs/krew/pull/350"))
    fmt.Println(isUrl("/home/rewanth/kubectl-fields/deploy/krew/plugin.yaml"))
    fmt.Println(isUrl("plugin.yaml"))
}

func isUrl(toTest string) bool {
    _, err := url.ParseRequestURI(toTest)
    if err != nil {
        return false
    } else {
        return true
    }
}

Output

true
true
false

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: rewanth1997
To complete the pull request process, please assign ahmetb
You can assign the PR to them by writing /assign @ahmetb in a comment when ready.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Oct 14, 2019
@rewanthtammana
Copy link
Author

rewanth@ubuntu:/tmp/krew/out/bin$ ./krew-linux_amd64 install --manifest=https://raw.githubusercontent.com/rewanth1997/kubectl-fields/master/deploy/krew/plugin.yaml
F1013 22:59:14.313896  107113 root.go:58] Cannot install plugin directly from URL
To install from URL, use --manifest <(curl https://raw.githubusercontent.com/rewanth1997/kubectl-fields/master/deploy/krew/plugin.yaml)

@corneliusweig I added an additional check to check for Schema and Host to validate the URL. Please review the PR.

@corneliusweig
Copy link
Contributor

Good find! I'll consider adding the same logic in the other PR.

Can you also add some tests for this behavior? Otherwise this looks good. Thanks!

@rewanthtammana
Copy link
Author

rewanthtammana commented Oct 14, 2019

I validated the module independently with different protocols like SMB, FTP, HTTP, etc. I believe these checks are good enough for integration. Please let me know if something else needs to be done.

package main

import (
    "fmt"
    "net/url"
)

func main() {
    fmt.Println(isUrl("http://github.com/kubernetes-sigs/krew/pull/350"))
    fmt.Println(isUrl("https://github.com/kubernetes-sigs/krew/pull/350"))
    fmt.Println(isUrl("ftp://mydomain/krew/plugin.yaml"))
    fmt.Println(isUrl("smb://mywindowsdomain/krew/plugin.yaml"))

    fmt.Println(isUrl("/home/rewanth/kubectl-fields/deploy/krew/plugin.yaml"))
    fmt.Println(isUrl("http//plugin.yaml"))
    fmt.Println(isUrl("plugin.yaml"))
}

func isUrl(toTest string) bool {
    u, err := url.ParseRequestURI(toTest)
    if err == nil && u.Scheme != "" && u.Host != "" {
        return true
    } else {
        return false
    }
}

Output

true
true
true
true
false
false
false

Goplayground link to run tests: https://play.golang.org/p/xUN0SHp1c9t

@ahmetb
Copy link
Member

ahmetb commented Oct 14, 2019

@corneliusweig have we settled on --manifest vs --manifest-url?
It seems like latter is more verbose (as opposed to magic) and can be easily implemented, albeit with extra testing requirements.

cmd/krew/cmd/install.go Outdated Show resolved Hide resolved
cmd/krew/cmd/internal/downloadFile.go Outdated Show resolved Hide resolved
cmd/krew/cmd/install.go Outdated Show resolved Hide resolved
cmd/krew/cmd/install.go Show resolved Hide resolved
cmd/krew/cmd/install.go Outdated Show resolved Hide resolved
Uses random port instead of hard-coded port.
Closes http response object
@rewanthtammana
Copy link
Author

@ahmetb @corneliusweig

All the tests are successful on my local system. Just to confirm, I forked my current working repository (https://github.com/rewanth1997/krew/tree/manifest-url) into my test account (https://github.com/testinguser883/krew/tree/manifest-url) and the build is successful there also (https://travis-ci.org/testinguser883/krew/builds/622366453). I'm not sure why travis is failing here.

I made all the suggested changes expect --archive and --manifest-url conflict.

I added the abstraction layer, removed hard-coded port number, closed response object after use and fixed a few linter bugs as well. Please review the latest code.

@corneliusweig
Copy link
Contributor

@rewanth1997 I think you need to rebase your current branch on top of origin/master. Here is what you need to do:

git remote add krew-origin [email protected]:kubernetes-sigs/krew.git
git fetch krew-origin
git rebase krew-origin/master

I guess you will have to resolve a few merge conflicts.

Copy link
Contributor

@corneliusweig corneliusweig left a comment

Choose a reason for hiding this comment

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

Ok, this looks much better now. Left a few more comments, but that should be easy to do.

cmd/krew/cmd/internal/read_plugin.go Outdated Show resolved Hide resolved
cmd/krew/cmd/internal/read_plugin.go Outdated Show resolved Hide resolved
cmd/krew/cmd/internal/read_plugin.go Outdated Show resolved Hide resolved
cmd/krew/cmd/internal/read_plugin.go Outdated Show resolved Hide resolved
cmd/krew/cmd/internal/read_plugin.go Show resolved Hide resolved
cmd/krew/cmd/install.go Show resolved Hide resolved
@rewanthtammana
Copy link
Author

@corneliusweig All recommended changes updated.

@rewanthtammana
Copy link
Author

@corneliusweig Even the mutually exclusive condition is fixed in the latest commit. All tests are passed locally. Please let me know if any further changes are required.

@ahmetb
Copy link
Member

ahmetb commented Dec 9, 2019

The tests are failing with a very unusual error.
I'll make another review or push commits directly to your branch, thanks for staying on top of this @rewanth1997.

Comment on lines +113 to +123
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
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.

Comment on lines +173 to +187
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")
}
}
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?

Comment on lines +189 to +203
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")
}
}
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?

Comment on lines +42 to +49
// GetPlugin is an abstraction layer for manifest and manifest-url handlers and returns plugin object
func GetPlugin(manifest string, manifestURL string) (index.Plugin, error) {
if manifest != "" {
return indexscanner.ReadPluginFile(manifest)
}

return readRemotePluginManifest(manifestURL)
}
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.

Comment on lines +27 to +40
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
}
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.

@@ -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.

@ahmetb
Copy link
Member

ahmetb commented Dec 9, 2019

I think this PR has showed us fundamentally how we should approach accepting patches to krew codebase.

To me, minimalism is key, and ideally we should not require ~200 lines of new code to add one flag like this.

I'll be preparing a PR that does the same thing. I think some issues in the PR stems from the fact that our indexscanner API isn't the best it could be. Let's see how much new code it'll end up in.

@@ -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.

@ahmetb
Copy link
Member

ahmetb commented Dec 9, 2019

/hold
In about an hour I prepared a patch that does this: #429
It only results in ~120 lines of net added code while addressing the underlying technical debt and adding some missing tests.

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Dec 9, 2019
@rewanthtammana
Copy link
Author

The tests are failing with a very unusual error.

Yes, even I'm not sure why? This branch is building successfully without any errors
https://travis-ci.org/testinguser883/krew/builds/622405722?utm_source=github_status&utm_medium=notification

It would be great if you can point out why the build is failing here (maybe it's because this branch is not synced with the current master repo?)

@ahmetb
Copy link
Member

ahmetb commented Dec 9, 2019

Yeah, most likely you’ll need to rebase.
On the other hand, we can continue on my PR as we’re getting to ~100 comments on this and we still have some way to go. I think my patch addresses this more cleanly, but thank you for doing the work on this.

@rewanthtammana
Copy link
Author

Yeah, sure. We will work on your PR. I will try to give inputs to the best of my knowledge.

@corneliusweig @ahmetb Thanks for considering my idea, being so patient and reviewing my PR.

@corneliusweig
Copy link
Contributor

Hey @rewanth1997, sorry that your PR doesn't make it into krew's codebase. Don't be upset though. The final draft was already much more mature than the initial one, so you probably could learn a lot by doing it. For your future work, you should focus on testing your code. You can learn a lot from looking at @ahmetb's version :)

/close

@k8s-ci-robot
Copy link
Contributor

@corneliusweig: Closed this PR.

In response to this:

Hey @rewanth1997, sorry that your PR doesn't make it into krew's codebase. Don't be upset though. The final draft was already much more mature than the initial one, so you probably could learn a lot by doing it. For your future work, you should focus on testing your code. You can learn a lot from looking at @ahmetb's version :)

/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants