-
Notifications
You must be signed in to change notification settings - Fork 38.9k
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
[Federation][join-01] Implement kubefed join
command.
#35493
[Federation][join-01] Implement kubefed join
command.
#35493
Conversation
d30a682
to
55d633b
Compare
join_long = templates.LongDesc(` | ||
Join a cluster to a federation. | ||
|
||
Current context is assumed to be a federation endpoint. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"federation endpoint" is not obvious in what it means. How about "is assumed to be for federation control plane" or "is assumed to be for federation apiserver"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// `PathOptions()`) and a mechanism to talk to the federation host | ||
// cluster. | ||
type JoinFederationConfig interface { | ||
// PathOptions provides filesystem based kubeconfig access. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kubeconfig access to what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is kubeconfig-access. In other words access to kubeconfig. I thought about "filesystem based access to kubeconfig", but that doesn't sound right in this context, so wrote it this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
HostFactory(host, kubeconfigPath string) cmdutil.Factory | ||
} | ||
|
||
// joinFederationConfig implements JoinFederationConfig interface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add var _ JoinFederationConfig = &joinFederationConfig{}
to verify and document that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
|
||
func (r *joinFederationConfig) PathOptions() *clientcmd.PathOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: why use r for joinFederationConfig? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed from r
to j
.
You might have already guessed the reason behind r
. This receiver struct did not start its life with this name. It started as registerFederationConfig
:-)
po.LoadingRules.ExplicitPath = kubeconfig | ||
clientConfig, err := po.GetStartingConfig() | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its generally recommended to add some context while returning error message. Like
return fmt.Errorf("error %s in getting starting config for path options: %v", po, err)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of fmt
(it is actually whatever returned by golang/pkg/errors.New()
). That's a type precision loss and that means cmdutil.CheckErr(err)
in the caller has no idea about the type of the error and can't do the right thing. So I think we should just return the received error without coercing it to a string.
|
||
// Build the secret object with the minified and flattened | ||
// kubeconfig content. | ||
secret := &api.Secret{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use versioned clientset and create versioned secret object here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cmdutil.Factory
doesn't support external types yet, so it is not possible. I started writing code that way, but then had to switch back. I know how hand-tied it feels ;-)
// host:port part of the endpoint URL. | ||
u, err := urlParse(cluster.Server) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse cluster endpoint %q for cluster %q", cluster.Server, name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also include err
in this error message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that!
Done.
} | ||
serverAddress := cluster.Server | ||
if u.scheme == "" { | ||
// Use "https" as the default scheme if the port isn't "80". It |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its better to leave this. Users will have unexpected successes/failures by changing the port number. As a user I would have no clue what is happening if I hit this.
PS: By leave this, I mean choose a default. Dont change it as per port number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you still decide to keep it, I will urge you to add 8080, since thats the port used by our local cluster up script :)
But still think removing this is best
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's a fair argument.
Dropping the port number based inference and just plain defaulting to https
. Also, removing the complex parsing logic and dramatically simplifying this portion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thx! :)
hostport = segs[1] | ||
} | ||
|
||
remsegs := strings.SplitN(hostport, "/", 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whats rem in remsegs? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remaining? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Sorry, remsegs
wasn't obvious. Changed it to remainingSegs
for clarity.
if len(segs) == 2 { | ||
scheme = segs[0] | ||
hostport = segs[1] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could call net/url.Parse now once you have figured out the scheme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that, but it is uglier than this. You need to check if the scheme was already in the string prefix. And only if not you need to stick it in front of the string and then parse it. This is easier/less uglier.
High level comments:
|
55d633b
to
1c5c307
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikhiljindal addressed the comments. PTAL.
join_long = templates.LongDesc(` | ||
Join a cluster to a federation. | ||
|
||
Current context is assumed to be a federation endpoint. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// `PathOptions()`) and a mechanism to talk to the federation host | ||
// cluster. | ||
type JoinFederationConfig interface { | ||
// PathOptions provides filesystem based kubeconfig access. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is kubeconfig-access. In other words access to kubeconfig. I thought about "filesystem based access to kubeconfig", but that doesn't sound right in this context, so wrote it this way.
HostFactory(host, kubeconfigPath string) cmdutil.Factory | ||
} | ||
|
||
// joinFederationConfig implements JoinFederationConfig interface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
|
||
func (r *joinFederationConfig) PathOptions() *clientcmd.PathOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed from r
to j
.
You might have already guessed the reason behind r
. This receiver struct did not start its life with this name. It started as registerFederationConfig
:-)
po.LoadingRules.ExplicitPath = kubeconfig | ||
clientConfig, err := po.GetStartingConfig() | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of fmt
(it is actually whatever returned by golang/pkg/errors.New()
). That's a type precision loss and that means cmdutil.CheckErr(err)
in the caller has no idea about the type of the error and can't do the right thing. So I think we should just return the received error without coercing it to a string.
|
||
// Build the secret object with the minified and flattened | ||
// kubeconfig content. | ||
secret := &api.Secret{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cmdutil.Factory
doesn't support external types yet, so it is not possible. I started writing code that way, but then had to switch back. I know how hand-tied it feels ;-)
// host:port part of the endpoint URL. | ||
u, err := urlParse(cluster.Server) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse cluster endpoint %q for cluster %q", cluster.Server, name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that!
Done.
if len(segs) == 2 { | ||
scheme = segs[0] | ||
hostport = segs[1] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that, but it is uglier than this. You need to check if the scheme was already in the string prefix. And only if not you need to stick it in front of the string and then parse it. This is easier/less uglier.
hostport = segs[1] | ||
} | ||
|
||
remsegs := strings.SplitN(hostport, "/", 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Sorry, remsegs
wasn't obvious. Changed it to remainingSegs
for clarity.
} | ||
serverAddress := cluster.Server | ||
if u.scheme == "" { | ||
// Use "https" as the default scheme if the port isn't "80". It |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's a fair argument.
Dropping the port number based inference and just plain defaulting to https
. Also, removing the complex parsing logic and dramatically simplifying this portion.
1c5c307
to
3f07063
Compare
@nikhiljindal PTAL.
I have added log statements where I think is appropriate.
Explained inline why this shouldn't be done in this case. But also pasting here. I would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of
Yeah. I am trying not to blindly copy the structure unless there is a good reason/explanation. |
7e7283a
to
a542b33
Compare
Jenkins GKE smoke e2e failed for commit a542b337d1435f67db7c3ee8a1f24ed62ca5d219. Full PR test history. The magic incantation to run this job again is |
a542b33
to
e1a2752
Compare
Only remaining comment is regarding ensuring that secret name is valid. |
@nikhiljindal after much deliberation, I am leaning towards not doing an automatic conversion at all. It might surprise users. And we have to explain how we do this conversion. And that conversion might break in several cases: len(context) > 256 for 2 clusters that when truncated aren't unique, some characters we miss/fail to convert and all the edge cases that come with it. Instead we should just provide a way to customize these values via flags, so that users have more control over how these names are created if they don't confer to RFC1123 subdomain spec and have a way to reason about it. I will add open an issue for it and implement it in a follow up PR. What do you think? |
e1a2752
to
ca59772
Compare
Jenkins GCE Node e2e failed for commit ca59772. Full PR test history. The magic incantation to run this job again is |
Yes Adding those flags in a separate PR is fine. |
ca59772
to
0ac3e3d
Compare
0ac3e3d
to
c862ffb
Compare
Jenkins verification failed for commit c862ffbf3e169ac4dc1e029ff72a77c853f56de8. Full PR test history. The magic incantation to run this job again is |
c862ffb
to
215a629
Compare
Jenkins unit/integration failed for commit 215a629. Full PR test history. The magic incantation to run this job again is |
@k8s-bot unit test this |
@k8s-bot verify test this |
Also, add unit tests for `kubefed join`.
215a629
to
2342f6e
Compare
PR #35492 was merged. Adding the LGTM label. |
@k8s-bot test this [submit-queue is verifying that this PR is safe to merge] |
Automatic merge from submit-queue |
Automatic merge from submit-queue [Federation][unjoin-00] Implement `kubefed unjoin` command. Please review only the last commit here. This is based on PR #35493 which will be reviewed independently. I will add a release note separately for this entire feature, so please don't worry too much about the release note here in the PR. Design Doc: PR #34484 cc @kubernetes/sig-cluster-federation @quinton-hoole @nikhiljindal
Supersedes PR #35155.
Please review only the last commit here. This is based on PR #35492 which will be reviewed independently.
I will add a release note separately for this entire feature, so please don't worry too much about the release note here in the PR.
Design Doc: PR #34484
cc @kubernetes/sig-cluster-federation @quinton-hoole @mwielgus
This change is![Reviewable](https://camo.githubusercontent.com/2d899f4291d07d3cd2fa4aaae1e3b243f164c23fce87d30a589ace0d496a444c/68747470733a2f2f72657669657761626c652e6b756265726e657465732e696f2f7265766965775f627574746f6e2e737667)