diff --git a/federation/pkg/kubefed/init/BUILD b/federation/pkg/kubefed/init/BUILD index cbf4ab390dc..e515acbb949 100644 --- a/federation/pkg/kubefed/init/BUILD +++ b/federation/pkg/kubefed/init/BUILD @@ -21,6 +21,8 @@ go_library( "//pkg/api/resource:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/client/unversioned/clientcmd:go_default_library", + "//pkg/client/unversioned/clientcmd/api:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/util/cert:go_default_library", @@ -49,6 +51,7 @@ go_test( "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/client/restclient/fake:go_default_library", "//pkg/client/typed/dynamic:go_default_library", + "//pkg/client/unversioned/clientcmd:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/util/intstr:go_default_library", diff --git a/federation/pkg/kubefed/init/init.go b/federation/pkg/kubefed/init/init.go index d0df78623a0..28664906241 100644 --- a/federation/pkg/kubefed/init/init.go +++ b/federation/pkg/kubefed/init/init.go @@ -42,6 +42,8 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" certutil "k8s.io/kubernetes/pkg/util/cert" @@ -56,6 +58,7 @@ import ( const ( APIServerCN = "federation-apiserver" ControllerManagerCN = "federation-controller-manager" + AdminCN = "admin" HostClusterLocalDNSZoneName = "cluster.local." lbAddrRetryInterval = 5 * time.Second @@ -122,6 +125,7 @@ type entityKeyPairs struct { ca *triple.KeyPair server *triple.KeyPair controllerManager *triple.KeyPair + admin *triple.KeyPair } // initFederation initializes a federation control plane. @@ -194,6 +198,11 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman advertiseAddress = ips[0] } + endpoint := advertiseAddress + if advertiseAddress == "" && len(hostnames) > 0 { + endpoint = hostnames[0] + } + // 6. Create federation API server _, err = createAPIServer(hostClientset, initFlags.FederationSystemNamespace, serverName, image, serverCredName, pvc.Name, advertiseAddress) if err != nil { @@ -206,6 +215,13 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman return err } + // 8. Write the federation API server endpoint info, credentials + // and context to kubeconfig + err = updateKubeconfig(config, initFlags.Name, endpoint, entKeyPairs) + if err != nil { + return err + } + return printSuccess(cmdOut, ips, hostnames) } @@ -287,10 +303,15 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam if err != nil { return nil, fmt.Errorf("failed to create federation controller manager client key and certificate: %v", err) } + admin, err := triple.NewClientKeyPair(ca, AdminCN) + if err != nil { + return nil, fmt.Errorf("failed to create client key and certificate for an admin: %v", err) + } return &entityKeyPairs{ ca: ca, server: server, controllerManager: cm, + admin: admin, }, nil } @@ -310,7 +331,6 @@ func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, cr // Boilerplate to create the secret in the host cluster. return clientset.Core().Secrets(namespace).Create(secret) - } func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs) (*api.Secret, error) { @@ -532,3 +552,44 @@ func printSuccess(cmdOut io.Writer, ips, hostnames []string) error { _, err := fmt.Fprintf(cmdOut, "Federation API server is running at: %s\n", strings.Join(svcEndpoints, ", ")) return err } + +func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPairs *entityKeyPairs) error { + po := config.PathOptions() + kubeconfig, err := po.GetStartingConfig() + if err != nil { + return err + } + + // Populate API server endpoint info. + cluster := clientcmdapi.NewCluster() + // Prefix "https" as the URL scheme to endpoint. + if !strings.HasPrefix(endpoint, "https://") { + endpoint = fmt.Sprintf("https://%s", endpoint) + } + cluster.Server = endpoint + cluster.CertificateAuthorityData = certutil.EncodeCertPEM(entKeyPairs.ca.Cert) + + // Populate credentials. + authInfo := clientcmdapi.NewAuthInfo() + authInfo.ClientCertificateData = certutil.EncodeCertPEM(entKeyPairs.admin.Cert) + authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(entKeyPairs.admin.Key) + authInfo.Username = AdminCN + + // Populate context. + context := clientcmdapi.NewContext() + context.Cluster = name + context.AuthInfo = name + + // Update the config struct with API server endpoint info, + // credentials and context. + kubeconfig.Clusters[name] = cluster + kubeconfig.AuthInfos[name] = authInfo + kubeconfig.Contexts[name] = context + + // Write the update kubeconfig. + if err := clientcmd.ModifyConfig(po, *kubeconfig, true); err != nil { + return err + } + + return nil +} diff --git a/federation/pkg/kubefed/init/init_test.go b/federation/pkg/kubefed/init/init_test.go index 2b9382afeef..ec28d4ca473 100644 --- a/federation/pkg/kubefed/init/init_test.go +++ b/federation/pkg/kubefed/init/init_test.go @@ -42,6 +42,7 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/client/restclient/fake" "k8s.io/kubernetes/pkg/client/typed/dynamic" + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/intstr" @@ -125,6 +126,8 @@ func TestInitFederation(t *testing.T) { t.Errorf("[%d] expected error: %s, got: %s, output: %s", i, tc.expectedErr, cmdErrMsg, buf.String()) } } + + testKubeconfigUpdate(t, tc.federation, tc.lbIP, tc.kubeconfigGlobal, tc.kubeconfigExplicit) } } @@ -744,6 +747,60 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image s return f, nil } +func testKubeconfigUpdate(t *testing.T, federationName, lbIP, kubeconfigGlobal, kubeconfigExplicit string) { + filename := kubeconfigGlobal + if kubeconfigExplicit != "" { + filename = kubeconfigExplicit + } + config, err := clientcmd.LoadFromFile(filename) + if err != nil { + t.Errorf("Failed to open kubeconfig file: %v", err) + return + } + + cluster, ok := config.Clusters[federationName] + if !ok { + t.Errorf("No cluster info for %q", federationName) + return + } + endpoint := lbIP + if !strings.HasSuffix(lbIP, "https://") { + endpoint = fmt.Sprintf("https://%s", lbIP) + } + if cluster.Server != endpoint { + t.Errorf("Want federation API server endpoint %q, got %q", endpoint, cluster.Server) + } + + authInfo, ok := config.AuthInfos[federationName] + if !ok { + t.Errorf("No credentials for %q", federationName) + return + } + if len(authInfo.ClientCertificateData) == 0 { + t.Errorf("Expected client certificate to be non-empty") + return + } + if len(authInfo.ClientKeyData) == 0 { + t.Errorf("Expected client key to be non-empty") + return + } + if authInfo.Username != AdminCN { + t.Errorf("Want username: %q, got: %q", AdminCN, authInfo.Username) + } + + context, ok := config.Contexts[federationName] + if !ok { + t.Errorf("No context for %q", federationName) + return + } + if context.Cluster != federationName { + t.Errorf("Want context cluster name: %q, got: %q", federationName, context.Cluster) + } + if context.AuthInfo != federationName { + t.Errorf("Want context auth info: %q, got: %q", federationName, context.AuthInfo) + } +} + type clientServerTLSConfigs struct { server *tls.Config client *tls.Config