diff --git a/federation/pkg/kubefed/init/init.go b/federation/pkg/kubefed/init/init.go index 6c4a6e0986b..a07a26786ab 100644 --- a/federation/pkg/kubefed/init/init.go +++ b/federation/pkg/kubefed/init/init.go @@ -120,6 +120,7 @@ func NewCmdInit(cmdOut io.Writer, config util.AdminConfig) *cobra.Command { cmd.Flags().String("image", defaultImage, "Image to use for federation API server and controller manager binaries.") cmd.Flags().String("dns-provider", "google-clouddns", "Dns provider to be used for this deployment.") cmd.Flags().String("etcd-pv-capacity", "10Gi", "Size of persistent volume claim to be used for etcd.") + cmd.Flags().Bool("dry-run", false, "dry run without sending commands to server.") return cmd } @@ -142,6 +143,7 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman image := cmdutil.GetFlagString(cmd, "image") dnsProvider := cmdutil.GetFlagString(cmd, "dns-provider") etcdPVCapacity := cmdutil.GetFlagString(cmd, "etcd-pv-capacity") + dryRun := cmdutil.GetDryRunFlag(cmd) hostFactory := config.HostFactory(initFlags.Host, initFlags.Kubeconfig) hostClientset, err := hostFactory.ClientSet() @@ -155,17 +157,17 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman cmKubeconfigName := fmt.Sprintf("%s-kubeconfig", cmName) // 1. Create a namespace for federation system components - _, err = createNamespace(hostClientset, initFlags.FederationSystemNamespace) + _, err = createNamespace(hostClientset, initFlags.FederationSystemNamespace, dryRun) if err != nil { return err } // 2. Expose a network endpoint for the federation API server - svc, err := createService(hostClientset, initFlags.FederationSystemNamespace, serverName) + svc, err := createService(hostClientset, initFlags.FederationSystemNamespace, serverName, dryRun) if err != nil { return err } - ips, hostnames, err := waitForLoadBalancerAddress(hostClientset, svc) + ips, hostnames, err := waitForLoadBalancerAddress(hostClientset, svc, dryRun) if err != nil { return err } @@ -176,13 +178,13 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman return err } - _, err = createAPIServerCredentialsSecret(hostClientset, initFlags.FederationSystemNamespace, serverCredName, entKeyPairs) + _, err = createAPIServerCredentialsSecret(hostClientset, initFlags.FederationSystemNamespace, serverCredName, entKeyPairs, dryRun) if err != nil { return err } // 4. Create a kubeconfig secret - _, err = createControllerManagerKubeconfigSecret(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmKubeconfigName, entKeyPairs) + _, err = createControllerManagerKubeconfigSecret(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmKubeconfigName, entKeyPairs, dryRun) if err != nil { return err } @@ -190,7 +192,7 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman // 5. Create a persistent volume and a claim to store the federation // API server's state. This is where federation API server's etcd // stores its data. - pvc, err := createPVC(hostClientset, initFlags.FederationSystemNamespace, svc.Name, etcdPVCapacity) + pvc, err := createPVC(hostClientset, initFlags.FederationSystemNamespace, svc.Name, etcdPVCapacity, dryRun) if err != nil { return err } @@ -208,38 +210,46 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman } // 6. Create federation API server - _, err = createAPIServer(hostClientset, initFlags.FederationSystemNamespace, serverName, image, serverCredName, pvc.Name, advertiseAddress) + _, err = createAPIServer(hostClientset, initFlags.FederationSystemNamespace, serverName, image, serverCredName, pvc.Name, advertiseAddress, dryRun) if err != nil { return err } // 7. Create federation controller manager - _, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider) + _, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider, dryRun) if err != nil { return err } // 8. Write the federation API server endpoint info, credentials // and context to kubeconfig - err = updateKubeconfig(config, initFlags.Name, endpoint, entKeyPairs) + err = updateKubeconfig(config, initFlags.Name, endpoint, entKeyPairs, dryRun) if err != nil { return err } - return printSuccess(cmdOut, ips, hostnames) + if !dryRun { + return printSuccess(cmdOut, ips, hostnames) + } + _, err = fmt.Fprintf(cmdOut, "Federation control plane runs (dry run)\n") + return err } -func createNamespace(clientset *client.Clientset, namespace string) (*api.Namespace, error) { +func createNamespace(clientset *client.Clientset, namespace string, dryRun bool) (*api.Namespace, error) { ns := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: namespace, }, } + if dryRun { + return ns, nil + } + return clientset.Core().Namespaces().Create(ns) } -func createService(clientset *client.Clientset, namespace, svcName string) (*api.Service, error) { +func createService(clientset *client.Clientset, namespace, svcName string, dryRun bool) (*api.Service, error) { svc := &api.Service{ ObjectMeta: api.ObjectMeta{ Name: svcName, @@ -260,13 +270,21 @@ func createService(clientset *client.Clientset, namespace, svcName string) (*api }, } + if dryRun { + return svc, nil + } + return clientset.Core().Services(namespace).Create(svc) } -func waitForLoadBalancerAddress(clientset *client.Clientset, svc *api.Service) ([]string, []string, error) { +func waitForLoadBalancerAddress(clientset *client.Clientset, svc *api.Service, dryRun bool) ([]string, []string, error) { ips := []string{} hostnames := []string{} + if dryRun { + return ips, hostnames, nil + } + err := wait.PollImmediateInfinite(lbAddrRetryInterval, func() (bool, error) { pollSvc, err := clientset.Core().Services(svc.Namespace).Get(svc.Name) if err != nil { @@ -319,7 +337,7 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam }, nil } -func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, credentialsName string, entKeyPairs *entityKeyPairs) (*api.Secret, error) { +func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, credentialsName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) { // Build the secret object with API server credentials. secret := &api.Secret{ ObjectMeta: api.ObjectMeta{ @@ -333,11 +351,14 @@ func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, cr }, } + if dryRun { + return secret, nil + } // 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) { +func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) { basicClientConfig := kubeadmutil.CreateBasicClientConfig( name, fmt.Sprintf("https://%s", svcName), @@ -352,10 +373,10 @@ func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namesp certutil.EncodeCertPEM(entKeyPairs.controllerManager.Cert), ) - return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, false) + return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, dryRun) } -func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity string) (*api.PersistentVolumeClaim, error) { +func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity string, dryRun bool) (*api.PersistentVolumeClaim, error) { capacity, err := resource.ParseQuantity(etcdPVCapacity) if err != nil { return nil, err @@ -382,10 +403,14 @@ func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity s }, } + if dryRun { + return pvc, nil + } + return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc) } -func createAPIServer(clientset *client.Clientset, namespace, name, image, credentialsName, pvcName, advertiseAddress string) (*extensions.Deployment, error) { +func createAPIServer(clientset *client.Clientset, namespace, name, image, credentialsName, pvcName, advertiseAddress string, dryRun bool) (*extensions.Deployment, error) { command := []string{ "/hyperkube", "federation-apiserver", @@ -480,10 +505,14 @@ func createAPIServer(clientset *client.Clientset, namespace, name, image, creden }, } + if dryRun { + return dep, nil + } + return clientset.Extensions().Deployments(namespace).Create(dep) } -func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider string) (*extensions.Deployment, error) { +func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider string, dryRun bool) (*extensions.Deployment, error) { dep := &extensions.Deployment{ ObjectMeta: api.ObjectMeta{ Name: cmName, @@ -546,6 +575,9 @@ func createControllerManager(clientset *client.Clientset, namespace, name, svcNa }, } + if dryRun { + return dep, nil + } return clientset.Extensions().Deployments(namespace).Create(dep) } @@ -555,7 +587,7 @@ func printSuccess(cmdOut io.Writer, ips, hostnames []string) error { return err } -func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPairs *entityKeyPairs) error { +func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPairs *entityKeyPairs, dryRun bool) error { po := config.PathOptions() kubeconfig, err := po.GetStartingConfig() if err != nil { @@ -588,9 +620,11 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPair kubeconfig.AuthInfos[name] = authInfo kubeconfig.Contexts[name] = context - // Write the update kubeconfig. - if err := clientcmd.ModifyConfig(po, *kubeconfig, true); err != nil { - return err + if !dryRun { + // 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 0d5e680b0d2..a222429b932 100644 --- a/federation/pkg/kubefed/init/init_test.go +++ b/federation/pkg/kubefed/init/init_test.go @@ -79,6 +79,7 @@ func TestInitFederation(t *testing.T) { etcdPVCapacity string expectedErr string dnsProvider string + dryRun string }{ { federation: "union", @@ -90,6 +91,7 @@ func TestInitFederation(t *testing.T) { etcdPVCapacity: "5Gi", expectedErr: "", dnsProvider: "test-dns-provider", + dryRun: "", }, { federation: "union", @@ -101,9 +103,24 @@ func TestInitFederation(t *testing.T) { etcdPVCapacity: "", //test for default value of pvc-size expectedErr: "", dnsProvider: "", //test for default value of dns provider + dryRun: "", + }, + { + federation: "union", + kubeconfigGlobal: fakeKubeFiles[0], + kubeconfigExplicit: "", + dnsZoneName: "example.test.", + lbIP: "10.20.30.40", + image: "example.test/foo:bar", + etcdPVCapacity: "", + expectedErr: "", + dnsProvider: "test-dns-provider", + dryRun: "valid-run", }, } + //TODO: implement a negative case for dry run + for i, tc := range testCases { cmdErrMsg = "" dnsProvider = "" @@ -130,12 +147,16 @@ func TestInitFederation(t *testing.T) { cmd.Flags().Set("host-cluster-context", "substrate") cmd.Flags().Set("dns-zone-name", tc.dnsZoneName) cmd.Flags().Set("image", tc.image) - if "" != tc.dnsProvider { + if tc.dnsProvider != "" { cmd.Flags().Set("dns-provider", tc.dnsProvider) } - if "" != tc.etcdPVCapacity { + if tc.etcdPVCapacity != "" { cmd.Flags().Set("etcd-pv-capacity", tc.etcdPVCapacity) } + if tc.dryRun == "valid-run" { + cmd.Flags().Set("dry-run", "true") + } + cmd.Run(cmd, []string{tc.federation}) if tc.expectedErr == "" { @@ -143,6 +164,10 @@ func TestInitFederation(t *testing.T) { // Actual data passed are tested in the fake secret and cluster // REST clients. want := fmt.Sprintf("Federation API server is running at: %s\n", tc.lbIP) + if tc.dryRun != "" { + want = fmt.Sprintf("Federation control plane runs (dry run)\n") + } + if got := buf.String(); got != want { t.Errorf("[%d] unexpected output: got: %s, want: %s", i, got, want) if cmdErrMsg != "" {