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

add support for adopting external cluster #306

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

pdettori
Copy link
Collaborator

@pdettori pdettori commented Dec 9, 2024

Summary

Adds support for importing (adopting) external cluster as control plane

Related issue(s)

Fixes #163

Signed-off-by: Paolo Dettori <[email protected]>
cp := common.GenerateControlPlane(c.Name, string(controlPlaneType), "", hook, hookVars)

util.PrintStatus(fmt.Sprintf("Adopting control plane %s of type %s ...", c.Name, controlPlaneType), done, &wg, chattyStatus)
if err := cl.Create(context.TODO(), cp, &client.CreateOptions{}); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, all writes should supply a FieldManager in the XXXOptions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I prefer to leave this to a separate campaign/PR as there are many other places where there are writes so should probably be done consistently everywhere.

@MikeSpreitzer
Copy link
Contributor

Yes, WIP. This still needs a doc update. I do not understand how the user passes the kubeconfig of the cluster to be adopted into the adopt subcommand. The adopt subcommand needs to get added into the command tree.

@pdettori
Copy link
Collaborator Author

pdettori commented Dec 11, 2024

Yes, WIP. This still needs a doc update. I do not understand how the user passes the kubeconfig of the cluster to be adopted into the adopt subcommand. The adopt subcommand needs to get added into the command tree.

@MikeSpreitzer - correct on all points - I started with the easiest updates, now working on the "adopt" subcommand and getting the kubeconfig. The current idea is that the user will supply the name of the context (and optionally the path of the Kubeconfig) in the Kubeconfig for the cluster to adopt as flags of the adopt subcommand, and the such subcommand will take care of creating ClusterRoles, ClusterRoleBindings, SA and Token in the adopted cluster so that the Token + cluster endpoint + CA cert can be used to build a new Kubeconfig for the adopted cluster to store in a secret in the namespace associated with the control plane in the hosting cluster.

@pdettori pdettori changed the title ✨ WIP - add support for importing external cluster add support for adopting external cluster Dec 16, 2024
@francostellari
Copy link
Contributor

@pdettori I think you have some changes in chart/templates/operator.yaml that may be accidental

To create a control plane of type `external` with the required options, run the command:

```shell
kflex adopt --adopted-context <kubeconfig-context-of-external-cluster> cp5
Copy link
Contributor

Choose a reason for hiding this comment

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

Could a separated kubeconfig be passed with KUBECONFIG=... kflex... or `kflex --kubeconfig '?

Copy link
Contributor

Choose a reason for hiding this comment

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

if no value is passed to --adopted-context could it default to the current one... especially if one passes a separate kubeconfig with only one context in it?

Copy link
Contributor

Choose a reason for hiding this comment

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

As this command both create the secret and create the CP... it would be hard to use in combination with KS future Helm chart. The process would be

  1. create/push the secret in the cluster manually or via kflex.
  2. tell KS helm to create a CP using it (either upon installation or later on)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could a separated kubeconfig be passed with KUBECONFIG=... kflex... or `kflex --kubeconfig '?

Yes. These are all the options for the adopt subcommand. You'd be looking at the --adopted-kubeconfig flag.

$ kflex adopt --help
Adopt a control plane from an external cluster and switches the Kubeconfig context to
                the current instance

Usage:
  kflex adopt <name> [flags]

Flags:
  -c, --adopted-context string           path to adopted cluster context in adopted kubeconfig
  -a, --adopted-kubeconfig string        path to adopted cluster kubeconfig file. If unspecified, it uses the default Kubeconfig
  -s, --chatty-status                    chatty status indicator (default true)
  -x, --expiration-seconds int           adopted token expiration in seconds. Default is one year. (default 31536000)
  -h, --help                             help for adopt
  -k, --kubeconfig string                path to kubeconfig file
  -p, --postcreate-hook string           name of post create hook to run
  -e, --set stringArray                  set post create hook variables, in the form name=value 
  -d, --skip-url-override                skip URL override, used for local debugging only
  -u, --url-override https://127.0.0.1   URL overrride for adopted cluster. Required when cluster address uses local host address, e.g. https://127.0.0.1 
  -v, --verbosity int                    log level

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

if no value is passed to --adopted-context could it default to the current one... especially if one passes a separate kubeconfig with only one context in it?

Currently you do need to pass the context. It might be confusing in the common developer scenario where there is one kubeconfig with a context for each cluster, since the "current" context is typically the kubeflex hosting cluster context. It might be possible when a different Kubeconfig is used (which needs to be specified with --adopted-kubeconfig) to check if there is only one context and in that case not require the use of --adopted-context. But I would leave this as a future feature/PR if we really think it is useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As this command both create the secret and create the CP... it would be hard to use in combination with KS future Helm chart...

One possible approach for using kflex to create bootstrap secret and the helm chart to create the CP would be to add an option to the adopt subcommand --create-bootstrap-secret-only - would that help?

Copy link
Contributor

Choose a reason for hiding this comment

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

As this command both create the secret and create the CP... it would be hard to use in combination with KS future Helm chart...

One possible approach for using kflex to create bootstrap secret and the helm chart to create the CP would be to add an option to the adopt subcommand --create-bootstrap-secret-only - would that help?

Assuming that one has a kubeconfig for the external cluster, I guess one can use kubectl to do it... that's way I was asking for clarifications on what the secret contains

kubectl create secret generic <cluster1-bootstrap-secret> --from-file=<cluster1-key>=<cluster1-kubeconfig-path> --namespace <cluster1-bootstrap-namespace>

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since it is assumed that the bootstrap kubeconfig has only one context, you probably need first to extract a new kubeconfig only for the context of the adopted cluster, so you'd need to run first a command such as:

kubectl --kubeconfig=$SOURCE_KUBECONFIG config view --minify --flatten \
--context=$DESIRED_CONTEXT > $OUTPUT_KUBECONFIG

and then you can run the kubectl create secret generic ... command

Copy link
Contributor

Choose a reason for hiding this comment

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

Since a kubeconfig has a "current context", the natural default for --adopted-context is the current context of the adopted kubeconfig.

BootstrapSecretRef contains a reference to the kubeconfig used to bootstrap adoption of
an external cluster
properties:
inClusterKey:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please clarify what is inClusterKey?

Perhaps I missed, but I did not see a documentation of what the bootstrap secret should look like. This is useful if somebody wants/needs to create it without using kflex.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You may see the defaults being used for the bootstrap secret in cmd/kflex/adopt/adopt.go. The name is defined in:

func GenerateBoostrapSecretName(cpName string) string {
	return fmt.Sprintf("%s-bootstrap", cpName)
}

namespace is SystemNamespace = "kubeflex-system" and key is KubeconfigSecretKeyInCluster = "kubeconfig-incluster"

Copy link
Contributor

Choose a reason for hiding this comment

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

To further clarify, inClusterKey is the name of the secret generated by kflex from the bootstrap secret... right?

Since you have a default, can you make it not required?

Copy link
Collaborator Author

@pdettori pdettori Dec 17, 2024

Choose a reason for hiding this comment

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

To further clarify, inClusterKey is the name of the secret generated by kflex from the bootstrap secret

No, inClusterKey is the name of the key used for the kubeconfig, which is kubeconfig-incluster. The name of the secret would be <cp-name>-bootstrap. You do not have to provide these values in the ControlPlane CR, they are optional.

Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 20, 2024

Choose a reason for hiding this comment

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

(A) Since the golang source re-uses the existing type SecretReference, regularity is implied here. It is a reference to the same sort of thing. Same schema for contents. (And, BTW, the expectation(s) on Secret contents should be documented in the comment on the SecretReference type.) I am not sure yet whether this usage really is regular.

(B) @pdettori, what do you mean by "You do not have to provide these values in the ControlPlane CR, they are optional"? Which values are optional?

BootstrapSecretRef *SecretReference `json:"bootstrapSecretRef,omitempty"`
// expiration time for token of adopted cluster
// +optional
AdoptedTokenExpirationSeconds *int64 `json:"adoptedTokenExpirationSeconds,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

(A) If the type is "external", shouldn't this field's name start with "External" rather than "Adopted"?

(B) Is this parameter really only relevant to the case of external type? Isn't this a question that arises for every type (possibly excepting host)?

@@ -591,7 +591,7 @@ spec:
- --secure-listen-address=0.0.0.0:8443
- --upstream=http://127.0.0.1:8080/
- --logtostderr=true
- --v={{.Values.verbosity}}
- --v=0
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this better than simply omitting the -v ?

@@ -280,6 +280,8 @@ clusters registration and support for the [`ManifestWork` API](https://open-clus
- vcluster: this is based on the [vcluster project](https://www.vcluster.com) and provides the ability to create pods in the hosting namespace of the hosting cluster.
- host: this control plane type exposes the underlying hosting cluster with the same control plane abstraction
used by the other control plane types.
- external: this control plane type exposes an external cluster with the same control plane abstraction
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect that the intended significance of "external" may not be clear to readers who do not already know what we are trying to say. I would have words here explaining that this type of ControlPlane is a representation of a preexisting cluster, one that was not created by KubeFlex. And not the hosting cluster.

Comment on lines +535 to +537
In this section, we will show an example of creating an external control plane to integrate
a kind cluster named `ext1` into the same Docker network as your Kubeflex hosting cluster. This setup
requires that both clusters are accessible from each other within the shared Docker network.
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this confusing. It says that the purpose is "to integrate a kind cluster named ext1 into the same Docker network as your Kubeflex hosting cluster" BUT it also says that they already have to be on the same Docker network. So the integration is already done?

I expected that the main issue here is the creation of a ControlPlane that represents a preexisting cluster.

docker network inspect kind | jq '.[].Containers | to_entries[] | .value.Name'
```

The output will list the container names for your clusters. For example:
Copy link
Contributor

Choose a reason for hiding this comment

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

"For example:" makes me expect that what's next is an example of the output from the above command. But that is not actually what comes next.

docker network ls
```

Inspect any specific network to confirm its configuration using docker network inspect <network_name>.
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't there a simpler way? Looking at the output of docker inspect cluster1-control-plane, for example, I see the following in the NetworkSettings.Networks.kind section (and "kind" is the only entry in NetworkSettings.Networks):

                    "DNSNames": [
                        "cluster1-control-plane",
                        "2e9a7ee1c5fe"
                    ]


In this section, we will show an example of creating an external control plane to integrate
a kind cluster named `ext1` into the same Docker network as your Kubeflex hosting cluster. This setup
requires that both clusters are accessible from each other within the shared Docker network.
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't it true that Docker supports multiple networks, so "the" shared network is not a general statement? Isn't it true that the real constraint here is that the "external" cluster and the KubeFlex hosting cluster are both on the same Docker network, regardless of which one that is?

To set up the external cluster ext1 as a control plane named cpe, use the following command:

```shell
$ kflex adopt -c kind-ext1 -u https://ext1-control-plane:6443
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 20, 2024

Choose a reason for hiding this comment

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

The meanings of "c" in -c and "u" in -u are not really obvious and I am never going to remember them. Since this is establishing a pattern for readers to follow, I would use the long flag names here.


### External clusters on a different host

If the external cluster operates on a different host and your kubeconfig context
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the "operates on a different host" really a critical precondition for not needing to specify a URL override?

@@ -314,6 +316,12 @@ To create a control plane of type `host` run the command:
kflex create cp4 --type host
```

Copy link
Contributor

Choose a reason for hiding this comment

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

Users of the API are going to need an adequate explanation too. The documentation below for CLI users omits all mention of the bootstrap secret.

@@ -314,6 +316,12 @@ To create a control plane of type `host` run the command:
kflex create cp4 --type host
```

To create a control plane of type `external` with the required options, run the command:
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 20, 2024

Choose a reason for hiding this comment

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

This command will also create a Secret object that lingers forever, right? If so then that seems like something worth mentioning. And if the Secret is eventually automatically deleted, users should be told about its temporary existence.

)

func (r *ExternalReconciler) ReconcileKubeconfigFromBoostrapSecret(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error {
_ = clog.FromContext(ctx)
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

What does this statement accomplish?

}

func deleteBoostrapSecret(crClient client.Client, ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error {
_ = clog.FromContext(ctx)
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

What does this statement, and all the others like it, accomplish?

return deleteBoostrapSecret(r.Client, ctx, hcp)
}

func getKubeconfigFromBoostrapSecret(crClient client.Client, ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) (*api.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.

Typo in the name here, missed the "t" before "strap"?

return r.Client.Update(ctx, kubeConfigSecret)
}

func deleteBoostrapSecret(crClient client.Client, ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo in this name, missed the "t" at the end of "Boot"?

return nil, err
}

key := util.DefaultString(hcp.Spec.BootstrapSecretRef.Key, util.KubeconfigSecretKeyInCluster)
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't the external kubeconfig the one commonly used? The whole point of adopting an external cluster is that it is external to the KubeConfig hosting cluster.

return fmt.Errorf("error creating ClusterRoleBinding on the adopted cluster: %v", err)
}

if err = reconcileAdoptedServiceAccount(aClientset.CoreV1().ServiceAccounts(adoptedClusterSANamespace), adoptedClusterSAName); err != nil {
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

Shouldn't ctx be passed along, in this and following calls?


kubeConfig := api.NewConfig()

kubeConfig.Clusters[context.Cluster] = &api.Cluster{
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't there additional fields of cluster that should be copied? Is there a reason to not copy cluster as a whole rather than a few enumerated fields?

Token: token,
}

kubeConfig.Contexts[hcp.Name] = &api.Context{
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not copy the default namespace and the extensions?

Namespace: namespace,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{util.KubeconfigSecretKeyInCluster: kubeconfig},
Copy link
Contributor

Choose a reason for hiding this comment

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

Is util.KubeconfigSecretKeyInCluster the right key (and the only right key)? Isn't this kubeconfig usable from anywhere?

@@ -137,7 +137,8 @@ func IsAPIServerDeploymentReady(log logr.Logger, c client.Client, hcp tenancyv1a
switch hcp.Spec.Type {

case tenancyv1alpha1.ControlPlaneTypeHost:
// host is always available
case tenancyv1alpha1.ControlPlaneTypeExternal:
// host or external is always available
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

This line is only reached when hcp.Spec.Type is tenancyv1alpha1.ControlPlaneTypeExternal. When hcp.Spec.Type is tenancyv1alpha1.ControlPlaneTypeHost), the previous clause is reached --- and now does nothing (whereas it formerly did a return true, nil). I suspect that you meant instead of two clauses to have one clause with two matching expressions.

cx.Context(chattyStatus, false, false, false)

controlPlaneType := tenancyv1alpha1.ControlPlaneTypeExternal
util.PrintStatus(fmt.Sprintf("Adopting control plane %s of type %s ...", c.Name, controlPlaneType), done, &wg, chattyStatus)
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you know that a Cobra Command has methods like Context, ErrOrStderr, OutOrStdout?

controlPlaneType := tenancyv1alpha1.ControlPlaneTypeExternal
util.PrintStatus(fmt.Sprintf("Adopting control plane %s of type %s ...", c.Name, controlPlaneType), done, &wg, chattyStatus)

adoptedKubeconfig := getAdoptedKubeconfig(c)
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

Since there are two kubeconfigs for the adopted cluster (one used for its bootstrap Secret, one used for the long-lived Secret), I do not find the names here clear. Would "bootstrap" be better than "adopted" here?

@@ -201,6 +231,17 @@ func init() {
createCmd.Flags().BoolVarP(&chattyStatus, "chatty-status", "s", true, "chatty status indicator")
createCmd.Flags().StringArrayVarP(&hookVars, "set", "e", []string{}, "set post create hook variables, in the form name=value ")

adoptCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "path to kubeconfig file")
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

The reader should be told which kubeconfig. There are three involved in adoption: one for the KubeFlex hosting cluster, one to read/write the bootstrap Secret, and one constructed for users of the ControlPlane.

Comment on lines +162 to +166
AdoptedKubeconfig: adoptedKubeconfig,
AdoptedContext: adoptedContext,
AdoptedURLOverride: adoptedURLOverride,
AdoptedTokenExpirationSeconds: adoptedTokenExpirationSeconds,
SkipURLOverride: skipURLOverride,
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

FYI, you wouldn't have to do this copying if the subcommand options were in a struct that contained the cobra.Command rather than (roughly) the other way around. The OCM status addon also shows two more patterns that lack such copying.

return getKubeConfigFromEnv(c.Kubeconfig)
}

func getKubeConfigFromEnv(kubeconfig string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this func take a kubeconfig string argument? The one and only call site guarantees that the string is empty.

fmt.Fprintf(os.Stderr, "Error getting kubeflex client: %v\n", err)
os.Exit(1)
}
cl := *clp
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

Why does kfclient.GetClient return a pointer? Every call site does nothing with that pointer but dereference it --- to get an interface value that could itself be nil.

@@ -201,6 +231,17 @@ func init() {
createCmd.Flags().BoolVarP(&chattyStatus, "chatty-status", "s", true, "chatty status indicator")
createCmd.Flags().StringArrayVarP(&hookVars, "set", "e", []string{}, "set post create hook variables, in the form name=value ")

adoptCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "path to kubeconfig file")
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 23, 2024

Choose a reason for hiding this comment

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

Is there a default to the KUBECONFIG environment variable with standard semantics? Readers should probably be told the answer. And the answer should probably be "yes". FYI, in https://github.com/kubestellar/kubestellar/blob/v0.14.2/pkg/client-options/options.go we had the standard semantics and common flags wrapped up in a neat little bundle.

endpoint := adoptedURLOverride
if endpoint == "" {
endpoint = cluster.Server
if !isValidServerURL(endpoint) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this validity not checked for the case where adoptedURLOverride is not the empty string?


If the external cluster operates on a different host and your kubeconfig context
contains an endpoint for that cluster which is not using a loopback interface,
specifying an override URL is unnecessary.
Copy link
Contributor

Choose a reason for hiding this comment

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

In the current draft, I think the reader at this point is surprised by talk about an override URL. The reader has not seen this term before and does not know what it is (much less that the -u above was doing that).

adoptCmd.Flags().IntVarP(&verbosity, "verbosity", "v", 0, "log level") // TODO - figure out how to inject verbosity
adoptCmd.Flags().StringVarP(&Hook, "postcreate-hook", "p", "", "name of post create hook to run")
adoptCmd.Flags().BoolVarP(&chattyStatus, "chatty-status", "s", true, "chatty status indicator")
adoptCmd.Flags().BoolVarP(&skipURLOverride, "skip-url-override", "d", false, "skip URL override, used for local debugging only")
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

Why is there a --skip-url-override option? Is there any reason for this bit to be different from override == "" ?

// check if the current server URL in the adopted cluster kubeconfig is using
// a local address, which would not work in a container
func isValidServerURL(serverURL string) bool {
localAddresses := []string{"127.0.0.1", "localhost", "::1"}
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

strings.Contains("2600:0062::1", "::1") is true.
strings.Contains("nonlocalhost", "localhost") is true.
strings.Contains("127.0.0.100", "127.0.0.1") is true (and strings.Contains("127.0.0.2", "127.0.0.1") is false).

}

// check if the current server URL in the adopted cluster kubeconfig is using
// a local address, which would not work in a container
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

Is there no concern for any other validity? What if the user puts junk on the command line, junk that does not fit the syntax for a URL? Or junk that is a URL but has stuff (such as a query) that should not appear in a server URL?

kubeConfig.Contexts[contextName] = &api.Context{
Cluster: context.Cluster,
AuthInfo: contextName,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What about the Context's Extensions?

Namespace: util.SystemNamespace,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{util.KubeconfigSecretKeyInCluster: kubeconfig},
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

Isn't this actually a not-in-cluster case (at least usually)?

// +optional
BootstrapSecretRef *SecretReference `json:"bootstrapSecretRef,omitempty"`
// expiration time for token of adopted cluster
// +optional
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

When a field is optional, the doc on it needs to say what the default value is. And that default needs to be implemented (e.g., by a kubebuilder comment).

Backend BackendDBType `json:"backend,omitempty"`
// BootstrapSecretRef contains a reference to the kubeconfig used to bootstrap adoption of
// an external cluster
// +optional
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

This is an "inline union": some unconditional fields plus a discriminator (Type) that determines whether some of the other fields should be present or absent. FYI, these are frowned upon in the Kubernetes API design community. They prefer pure unions: a pure union struct has only a discriminator plus fields whose proper presence is determined by the discriminator.

Namespace: util.SystemNamespace,
InClusterKey: util.KubeconfigSecretKeyInCluster,
}
}
Copy link
Contributor

@MikeSpreitzer MikeSpreitzer Dec 24, 2024

Choose a reason for hiding this comment

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

What sets cp.Spec.AdoptedTokenExpirationSeconds?

split := strings.SplitN(pair, "=", 2)
if len(split) == 2 {
params[split[0]] = split[1]
}
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 the user should be given an error message if len(split) != 2.

Copy link
Contributor

@MikeSpreitzer MikeSpreitzer left a comment

Choose a reason for hiding this comment

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

Finished a review. Left individual comments.

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.

feature: import external cluster as control plane
3 participants