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

use client-go to port-forward to dashboard #76

Merged
merged 2 commits into from
Dec 6, 2017
Merged

use client-go to port-forward to dashboard #76

merged 2 commits into from
Dec 6, 2017

Conversation

prydonius
Copy link
Contributor

@prydonius prydonius commented Dec 1, 2017

Now that we have updated to client-go v4.0.0, we can use the library to
port-forward to the ingress controller.

This implementation is based on what kubectl is doing (pre-spdy package). See https://github.com/kubernetes/kubernetes/blob/2612e0c78ad18ac87bbd200d547100cf99f36089/pkg/kubectl/cmd/portforward.go#L104

fixes #64

Now that we have updated to client-go v4.0.0, we can use the library to
port-forward to the ingress controller.
- package: github.com/emicklei/go-restful-swagger12
version: dcef7f55730566d41eae5db10e7d6981829720f6
- package: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to lock these as the new versions are causing compilation errors

cmd/dashboard.go Outdated
// Open the Dashboard in a browser when the port-forward is established
go func() {
<-readyChannel
openInBrowser(fmt.Sprintf("http://localhost:%d", localPort))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

we now open the dashboard in the browser after the port-forward connection is established, so this fixes #64

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

cmd/dashboard.go Outdated
return nil, err
}
config.APIPath = "/api"
config.GroupVersion = &v1.SchemeGroupVersion
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to specifically set APIPath and GroupVersion otherwise the RestClient we get from this won't build the correct URL to port-forward.

cmd/dashboard.go Outdated
}

func openInBrowser(url string) error {
fmt.Printf("Opening %s in your default browser...\n", url)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thought this might be useful in case it doesn't open in the browser, the user can see what URL to go to.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Overall it LGTM, I left a few minor comments/questions. We could add some unit tests to cover the newPortforwarder (just to check that there are not any obvious error) but all this feature is calling for an end-to-end test at some point.

cmd/dashboard.go Outdated
}

func portforwardReqURL(config *rest.Config, podName string) (*url.URL, error) {
restClient, err := rest.RESTClientFor(config)
Copy link
Contributor

Choose a reason for hiding this comment

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

the restClient should be a parameter to make this function testable (even if you don't test it now). As a general rule, any function that creates a client cannot be tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1 will change

cmd/dashboard.go Outdated
return portforward.New(dialer, ports, stopChannel, readyChannel, os.Stdout, os.Stderr)
}

func restClientConfig() (*rest.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you should probably move this function to the root file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a very specific client config just for building the port-forward URI, so I think it should stay here.

Copy link
Contributor

Choose a reason for hiding this comment

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

mm, I saw it generic, what is the specific part? maybe the config.NegotiatedSerializer? If that is the case I would specify somewhere that this restClient is specific for doing port-forward (apart from defining that in this file) so other people don't misuse it. (Since this function is available for the whole cmd package)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not generic, we're specifying the api path and group version so we get URLs prefixed with api/v1. I don't think we need to use this anywhere else do we? We should typically use the discovery API.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this looks like a regular v1 config. Can we just use clientset.CoreV1().RESTClient() (or whatever the correct function is) below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, so I wanted to, since this is actually what kubectl uses also, but @ngtuna suggested not using it in #41 (comment) to reduce the dependency. However, now that I think about it, I don't think this would be pulling in many more dependencies, it uses the same underlying stuff. @ngtuna what do you think about using clientset for both this and checking if the nginx-ingress pod is there too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yah I am seeing no problem now for using clientset as we generally used it in several functions. What I was saying meant that if we can do stuffs easily with the current-in-used resourceClient then we should keep using it

cmd/dashboard.go Outdated
}
config.APIPath = "/api"
config.GroupVersion = &v1.SchemeGroupVersion
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
Copy link
Contributor

@andresmgot andresmgot Dec 1, 2017

Choose a reason for hiding this comment

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

I am curious, what is the goal of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No idea, it's a required field by RESTClientFor

Copy link
Contributor

Choose a reason for hiding this comment

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

It's necessary to get the apimachinary code to do the correct serialisation/reserialising of objects.

cmd/dashboard.go Outdated
return syscall.Exec(cmd, args, env)
req := restClient.Post().
Resource("pods").
Namespace(ingressNamespace).
Copy link
Contributor

Choose a reason for hiding this comment

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

the "ingressNamespace" seems confusing, I know that it points to kubeapps but it would be probably better if you make that var a parameter and call it podNamespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about ingressPodNamespace?

Copy link
Contributor

Choose a reason for hiding this comment

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

you would be unnecessarily limiting the scope of the function to be used with ingress pods, isn't it? As far as I can see you can use this to obtain the URL for any kind of pod.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like we're trying to generalise this for no reason. Seems like a pre-optimisation, what other port-forward calls do we expect to make?

Copy link
Contributor

Choose a reason for hiding this comment

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

probably not, it is a generic suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, unless you feel strongly about it, I think we can leave as-is. If we want to use this in more places in the future we can move it out of this file and add a parameter. Sound good?

cmd/dashboard.go Outdated
return nil, err
}

ports := []string{fmt.Sprintf("%d:80", localPort)}
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't be 80 obtained somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe, but given we know for sure it's going to be port 80, I think we can avoid the extra API call.

Copy link
Contributor

Choose a reason for hiding this comment

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

well, other people (like me) may see it that obvious. For example you also know for sure what URL the port forward will use but anyway you are obtaining it just in case it changes (and for the sake of readability)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would argue the difference between building the port-forward request URL is that we control the ingress controller manifests. We don't control the Kubernetes API though, so that could easily change from version to version.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think Andres is just suggesting a comment, which seems reasonable. We don't want a future reader to have to work this out for themselves.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, I just didn't know where the 80 came from

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry I misunderstood, I thought you meant parsing the Service object to get the port. Will add a comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, that's what I meant, but as far as it is clear where it comes from I am fine

@prydonius
Copy link
Contributor Author

R.E. unit tests, indeed it is quite difficult, but we may be able to test some of these functions. An end-to-end test is needed, I agree.

Copy link
Contributor

@anguslees anguslees left a comment

Choose a reason for hiding this comment

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

Lgtm overall, nice improvement.

cmd/dashboard.go Outdated
return nil, err
}

ports := []string{fmt.Sprintf("%d:80", localPort)}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think Andres is just suggesting a comment, which seems reasonable. We don't want a future reader to have to work this out for themselves.

cmd/dashboard.go Outdated
}
config.APIPath = "/api"
config.GroupVersion = &v1.SchemeGroupVersion
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's necessary to get the apimachinary code to do the correct serialisation/reserialising of objects.

cmd/dashboard.go Outdated
return portforward.New(dialer, ports, stopChannel, readyChannel, os.Stdout, os.Stderr)
}

func restClientConfig() (*rest.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this looks like a regular v1 config. Can we just use clientset.CoreV1().RESTClient() (or whatever the correct function is) below?

@prydonius
Copy link
Contributor Author

@ngtuna @andresmgot PTAL!

@ngtuna
Copy link
Contributor

ngtuna commented Dec 5, 2017

@prydonius the last commit lgtm

@@ -183,3 +186,34 @@ func generateEncodedRandomPassword(s int) (string, error) {
pw := base64.URLEncoding.EncodeToString(b)
return base64.URLEncoding.EncodeToString([]byte(pw)), nil
}

func clientForGroupVersionKind(pool dynamic.ClientPool, disco discovery.DiscoveryInterface, gvk schema.GroupVersionKind, namespace string) (*dynamic.ResourceClient, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

seems that you are not using these functions any more, you can delete them

Copy link
Contributor

Choose a reason for hiding this comment

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

No this func is still being used by others

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved it to root.go because they're being used, yep

@prydonius prydonius merged commit d64770f into vmware-tanzu:master Dec 6, 2017
@prydonius prydonius deleted the real-dashboard-command branch December 6, 2017 09:24
prydonius pushed a commit to prydonius/kubeapps that referenced this pull request Mar 5, 2018
- Added single instance view
- Moved bindings list to single instance view
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The first time you run kubeapps dashboard the browser opens to a blank
4 participants