diff --git a/cmd/clusterctl/clusterdeployer/BUILD.bazel b/cmd/clusterctl/clusterdeployer/BUILD.bazel index 4d6f5b95082f..0fdbfaafc620 100644 --- a/cmd/clusterctl/clusterdeployer/BUILD.bazel +++ b/cmd/clusterctl/clusterdeployer/BUILD.bazel @@ -11,10 +11,10 @@ go_library( deps = [ "//cmd/clusterctl/clusterdeployer/bootstrap:go_default_library", "//cmd/clusterctl/clusterdeployer/clusterclient:go_default_library", + "//cmd/clusterctl/clusterdeployer/provider:go_default_library", "//cmd/clusterctl/phases:go_default_library", "//cmd/clusterctl/providercomponents:go_default_library", "//pkg/apis/cluster/v1alpha1:go_default_library", - "//pkg/util:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/klog:go_default_library", @@ -27,6 +27,7 @@ go_test( embed = [":go_default_library"], deps = [ "//cmd/clusterctl/clusterdeployer/clusterclient:go_default_library", + "//cmd/clusterctl/clusterdeployer/provider:go_default_library", "//pkg/apis/cluster/v1alpha1:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/cmd/clusterctl/clusterdeployer/clusterclient/clusterclient.go b/cmd/clusterctl/clusterdeployer/clusterclient/clusterclient.go index 661b75009266..fe58f422fd94 100644 --- a/cmd/clusterctl/clusterdeployer/clusterclient/clusterclient.go +++ b/cmd/clusterctl/clusterdeployer/clusterclient/clusterclient.go @@ -609,3 +609,39 @@ func ifErrRemove(pErr *error, path string) { } } } + +func GetClusterAPIObject(client Client, clusterName, namespace string) (*clusterv1.Cluster, *clusterv1.Machine, []*clusterv1.Machine, error) { + machines, err := client.GetMachineObjectsInNamespace(namespace) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "unable to fetch machines") + } + cluster, err := client.GetClusterObject(clusterName, namespace) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "unable to fetch cluster %s/%s", namespace, clusterName) + } + + controlPlane, nodes, err := ExtractControlPlaneMachine(machines) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "unable to fetch control plane machine in cluster %s/%s", namespace, clusterName) + } + return cluster, controlPlane, nodes, nil +} + +// ExtractControlPlaneMachine separates the machines running the control plane (singular) from the incoming machines. +// This is currently done by looking at which machine specifies the control plane version. +// TODO: Cleanup. +func ExtractControlPlaneMachine(machines []*clusterv1.Machine) (*clusterv1.Machine, []*clusterv1.Machine, error) { + nodes := []*clusterv1.Machine{} + controlPlaneMachines := []*clusterv1.Machine{} + for _, machine := range machines { + if util.IsControlPlaneMachine(machine) { + controlPlaneMachines = append(controlPlaneMachines, machine) + } else { + nodes = append(nodes, machine) + } + } + if len(controlPlaneMachines) != 1 { + return nil, nil, errors.Errorf("expected one control plane machine, got: %v", len(controlPlaneMachines)) + } + return controlPlaneMachines[0], nodes, nil +} diff --git a/cmd/clusterctl/clusterdeployer/clusterdeployer.go b/cmd/clusterctl/clusterdeployer/clusterdeployer.go index 05e033516e04..64a1e62c4790 100644 --- a/cmd/clusterctl/clusterdeployer/clusterdeployer.go +++ b/cmd/clusterctl/clusterdeployer/clusterdeployer.go @@ -17,39 +17,18 @@ limitations under the License. package clusterdeployer import ( - "io/ioutil" - "os" + "fmt" "strings" - "time" "github.com/pkg/errors" - "k8s.io/client-go/kubernetes" "k8s.io/klog" "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/bootstrap" "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/clusterclient" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider" "sigs.k8s.io/cluster-api/cmd/clusterctl/phases" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" - "sigs.k8s.io/cluster-api/pkg/util" ) -// Deprecated interface for Provider specific logic. Please do not extend or add. This interface should be removed -// once issues/158 and issues/160 below are fixed. -type ProviderDeployer interface { - // TODO: This requirement can be removed once after: https://github.com/kubernetes-sigs/cluster-api/issues/158 - GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) - // TODO: This requirement can be removed after: https://github.com/kubernetes-sigs/cluster-api/issues/160 - GetKubeConfig(cluster *clusterv1.Cluster, controlPlaneMachine *clusterv1.Machine) (string, error) -} - -type ProviderComponentsStore interface { - Save(providerComponents string) error - Load() (string, error) -} - -type ProviderComponentsStoreFactory interface { - NewFromCoreClientset(clientset *kubernetes.Clientset) (ProviderComponentsStore, error) -} - type ClusterDeployer struct { bootstrapProvisioner bootstrap.ClusterProvisioner clientFactory clusterclient.Factory @@ -73,14 +52,9 @@ func New( } } -const ( - retryKubeConfigReady = 10 * time.Second - timeoutKubeconfigReady = 20 * time.Minute -) - // Create the cluster from the provided cluster definition and machine list. -func (d *ClusterDeployer) Create(cluster *clusterv1.Cluster, machines []*clusterv1.Machine, provider ProviderDeployer, kubeconfigOutput string, providerComponentsStoreFactory ProviderComponentsStoreFactory) error { - controlPlaneMachine, nodes, err := extractControlPlaneMachine(machines) +func (d *ClusterDeployer) Create(cluster *clusterv1.Cluster, machines []*clusterv1.Machine, provider provider.Deployer, kubeconfigOutput string, providerComponentsStoreFactory provider.ComponentsStoreFactory) error { + controlPlaneMachine, nodes, err := clusterclient.ExtractControlPlaneMachine(machines) if err != nil { return errors.Wrap(err, "unable to separate control plane machines from node machines") } @@ -117,9 +91,14 @@ func (d *ClusterDeployer) Create(cluster *clusterv1.Cluster, machines []*cluster } klog.Info("Creating target cluster") - targetClient, err := d.createTargetClusterClient(bootstrapClient, provider, kubeconfigOutput, cluster.Name, cluster.Namespace) + targetKubeconfig, err := phases.GetKubeconfig(bootstrapClient, provider, kubeconfigOutput, cluster.Name, cluster.Namespace) + if err != nil { + return fmt.Errorf("unable to create target cluster kubeconfig: %v", err) + } + + targetClient, err := d.clientFactory.NewClientFromKubeconfig(targetKubeconfig) if err != nil { - return errors.Wrap(err, "unable to create target cluster") + return errors.Wrap(err, "unable to create target cluster client") } defer closeClient(targetClient, "target") @@ -197,35 +176,11 @@ func (d *ClusterDeployer) Delete(targetClient clusterclient.Client, namespace st return nil } -func (d *ClusterDeployer) createTargetClusterClient(bootstrapClient clusterclient.Client, provider ProviderDeployer, kubeconfigOutput string, clusterName, namespace string) (clusterclient.Client, error) { - cluster, controlPlane, _, err := getClusterAPIObject(bootstrapClient, clusterName, namespace) - if err != nil { - return nil, err - } - - klog.V(1).Info("Getting target cluster kubeconfig.") - targetKubeconfig, err := waitForKubeconfigReady(provider, cluster, controlPlane) - if err != nil { - return nil, errors.Wrap(err, "unable to get target cluster kubeconfig") - } - - if err = d.writeKubeconfig(targetKubeconfig, kubeconfigOutput); err != nil { - return nil, err - } - - targetClient, err := d.clientFactory.NewClientFromKubeconfig(targetKubeconfig) - if err != nil { - return nil, errors.Wrap(err, "unable to create target cluster client") - } - - return targetClient, nil -} - -func (d *ClusterDeployer) updateClusterEndpoint(client clusterclient.Client, provider ProviderDeployer, clusterName, namespace string) error { +func (d *ClusterDeployer) updateClusterEndpoint(client clusterclient.Client, provider provider.Deployer, clusterName, namespace string) error { // Update cluster endpoint. Needed till this logic moves into cluster controller. // TODO: https://github.com/kubernetes-sigs/cluster-api/issues/158 // Fetch fresh objects. - cluster, controlPlane, _, err := getClusterAPIObject(client, clusterName, namespace) + cluster, controlPlane, _, err := clusterclient.GetClusterAPIObject(client, clusterName, namespace) if err != nil { return err } @@ -240,7 +195,7 @@ func (d *ClusterDeployer) updateClusterEndpoint(client clusterclient.Client, pro return nil } -func (d *ClusterDeployer) saveProviderComponentsToCluster(factory ProviderComponentsStoreFactory, kubeconfigPath string) error { +func (d *ClusterDeployer) saveProviderComponentsToCluster(factory provider.ComponentsStoreFactory, kubeconfigPath string) error { clientset, err := d.clientFactory.NewCoreClientsetFromKubeconfigFile(kubeconfigPath) if err != nil { return errors.Wrap(err, "error creating core clientset") @@ -271,31 +226,6 @@ func (d *ClusterDeployer) applyClusterAPIComponentsWithPivoting(client, source c return nil } -func (d *ClusterDeployer) writeKubeconfig(kubeconfig string, kubeconfigOutput string) error { - const fileMode = 0666 - os.Remove(kubeconfigOutput) - return ioutil.WriteFile(kubeconfigOutput, []byte(kubeconfig), fileMode) -} - -func waitForKubeconfigReady(provider ProviderDeployer, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { - kubeconfig := "" - err := util.PollImmediate(retryKubeConfigReady, timeoutKubeconfigReady, func() (bool, error) { - klog.V(2).Infof("Waiting for kubeconfig on %v to become ready...", machine.Name) - k, err := provider.GetKubeConfig(cluster, machine) - if err != nil { - klog.V(4).Infof("error getting kubeconfig: %v", err) - return false, nil - } - if k == "" { - return false, nil - } - kubeconfig = k - return true, nil - }) - - return kubeconfig, err -} - func pivotNamespace(from, to clusterclient.Client, namespace string) error { if err := from.WaitForClusterV1alpha1Ready(); err != nil { return errors.New("cluster v1alpha1 resource not ready on source cluster") @@ -394,42 +324,6 @@ func deleteObjectsInNamespace(client clusterclient.Client, namespace string) err return nil } -func getClusterAPIObject(client clusterclient.Client, clusterName, namespace string) (*clusterv1.Cluster, *clusterv1.Machine, []*clusterv1.Machine, error) { - machines, err := client.GetMachineObjectsInNamespace(namespace) - if err != nil { - return nil, nil, nil, errors.Wrap(err, "unable to fetch machines") - } - cluster, err := client.GetClusterObject(clusterName, namespace) - if err != nil { - return nil, nil, nil, errors.Wrapf(err, "unable to fetch cluster %v in namespace %v", clusterName, namespace) - } - - controlPlaneMachine, nodes, err := extractControlPlaneMachine(machines) - if err != nil { - return nil, nil, nil, errors.Wrapf(err, "unable to fetch control plane machine in cluster %v in namespace %v", clusterName, namespace) - } - return cluster, controlPlaneMachine, nodes, nil -} - -// extractControlPlaneMachine separates the machines running the control plane (singular) from the incoming machines. -// This is currently done by looking at which machine specifies the control plane version. -// TODO: Cleanup. -func extractControlPlaneMachine(machines []*clusterv1.Machine) (*clusterv1.Machine, []*clusterv1.Machine, error) { - nodes := []*clusterv1.Machine{} - controlPlaneMachines := []*clusterv1.Machine{} - for _, machine := range machines { - if util.IsControlPlaneMachine(machine) { - controlPlaneMachines = append(controlPlaneMachines, machine) - } else { - nodes = append(nodes, machine) - } - } - if len(controlPlaneMachines) != 1 { - return nil, nil, errors.Errorf("expected one control plane machine, got: %v", len(controlPlaneMachines)) - } - return controlPlaneMachines[0], nodes, nil -} - func closeClient(client clusterclient.Client, name string) { if err := client.Close(); err != nil { klog.Errorf("Could not close %v client: %v", name, err) diff --git a/cmd/clusterctl/clusterdeployer/clusterdeployer_test.go b/cmd/clusterctl/clusterdeployer/clusterdeployer_test.go index b32d7c4075e8..74cd04cf3319 100644 --- a/cmd/clusterctl/clusterdeployer/clusterdeployer_test.go +++ b/cmd/clusterctl/clusterdeployer/clusterdeployer_test.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/clusterclient" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" ) @@ -59,12 +60,12 @@ func (p *testClusterProvisioner) GetKubeconfig() (string, error) { } type mockProviderComponentsStoreFactory struct { - NewFromCoreclientsetPCStore ProviderComponentsStore + NewFromCoreclientsetPCStore provider.ComponentsStore NewFromCoreclientsetError error NewFromCoreclientsetCapturedArgument *kubernetes.Clientset } -func (m *mockProviderComponentsStoreFactory) NewFromCoreClientset(clientset *kubernetes.Clientset) (ProviderComponentsStore, error) { +func (m *mockProviderComponentsStoreFactory) NewFromCoreClientset(clientset *kubernetes.Clientset) (provider.ComponentsStore, error) { m.NewFromCoreclientsetCapturedArgument = clientset return m.NewFromCoreclientsetPCStore, m.NewFromCoreclientsetError } @@ -809,7 +810,7 @@ func TestExtractControlPlaneMachine(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - actualControlPlane, actualNodes, actualError := extractControlPlaneMachine(tc.inputMachines) + actualControlPlane, actualNodes, actualError := clusterclient.ExtractControlPlaneMachine(tc.inputMachines) if tc.expectedError == nil && actualError != nil { t.Fatalf("%s: extractControlPlaneMachine(%q): gotError %q; wantError [nil]", tc.name, len(tc.inputMachines), actualError) diff --git a/cmd/clusterctl/clusterdeployer/provider/BUILD.bazel b/cmd/clusterctl/clusterdeployer/provider/BUILD.bazel new file mode 100644 index 000000000000..b6db884d1e1a --- /dev/null +++ b/cmd/clusterctl/clusterdeployer/provider/BUILD.bazel @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["provider.go"], + importpath = "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/cluster/v1alpha1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) diff --git a/cmd/clusterctl/clusterdeployer/provider/provider.go b/cmd/clusterctl/clusterdeployer/provider/provider.go new file mode 100644 index 000000000000..060f19858249 --- /dev/null +++ b/cmd/clusterctl/clusterdeployer/provider/provider.go @@ -0,0 +1,42 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "k8s.io/client-go/kubernetes" + clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" +) + +// Deployer is a deprecated interface for Provider specific logic. Please do not extend or add. This interface should be removed +// once issues/158 and issues/160 below are fixed. +type Deployer interface { + // TODO: This requirement can be removed once after: https://github.com/kubernetes-sigs/cluster-api/issues/158 + GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) + // TODO: This requirement can be removed after: https://github.com/kubernetes-sigs/cluster-api/issues/160 + GetKubeConfig(cluster *clusterv1.Cluster, master *clusterv1.Machine) (string, error) +} + +// ComponentsStore is an interface for saving and loading Provider Components +type ComponentsStore interface { + Save(providerComponents string) error + Load() (string, error) +} + +// ComponentsStoreFactory is an interface for creating ComponentsStores +type ComponentsStoreFactory interface { + NewFromCoreClientset(clientset *kubernetes.Clientset) (ComponentsStore, error) +} diff --git a/cmd/clusterctl/clusterdeployer/providercomponentsstorefactory.go b/cmd/clusterctl/clusterdeployer/providercomponentsstorefactory.go index ca3bccb97eb5..21d5d7dfb990 100644 --- a/cmd/clusterctl/clusterdeployer/providercomponentsstorefactory.go +++ b/cmd/clusterctl/clusterdeployer/providercomponentsstorefactory.go @@ -18,16 +18,17 @@ package clusterdeployer import ( "k8s.io/client-go/kubernetes" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider" "sigs.k8s.io/cluster-api/cmd/clusterctl/providercomponents" ) type factory struct { } -func NewProviderComponentsStoreFactory() ProviderComponentsStoreFactory { +func NewProviderComponentsStoreFactory() provider.ComponentsStoreFactory { return &factory{} } -func (f *factory) NewFromCoreClientset(clientset *kubernetes.Clientset) (ProviderComponentsStore, error) { +func (f *factory) NewFromCoreClientset(clientset *kubernetes.Clientset) (provider.ComponentsStore, error) { return providercomponents.NewFromClientset(clientset) } diff --git a/cmd/clusterctl/cmd/BUILD.bazel b/cmd/clusterctl/cmd/BUILD.bazel index f298d574ad15..da6d6d15270d 100644 --- a/cmd/clusterctl/cmd/BUILD.bazel +++ b/cmd/clusterctl/cmd/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "alpha_phase_apply_cluster_api_components.go", "alpha_phase_apply_machines.go", "alpha_phase_create_bootstrap_cluster.go", + "alpha_phase_get_kubeconfig.go", "alpha_phases.go", "create.go", "create_cluster.go", @@ -26,6 +27,7 @@ go_library( "//cmd/clusterctl/clusterdeployer:go_default_library", "//cmd/clusterctl/clusterdeployer/bootstrap:go_default_library", "//cmd/clusterctl/clusterdeployer/clusterclient:go_default_library", + "//cmd/clusterctl/clusterdeployer/provider:go_default_library", "//cmd/clusterctl/phases:go_default_library", "//cmd/clusterctl/providercomponents:go_default_library", "//cmd/clusterctl/validation:go_default_library", diff --git a/cmd/clusterctl/cmd/alpha_phase_get_kubeconfig.go b/cmd/clusterctl/cmd/alpha_phase_get_kubeconfig.go new file mode 100644 index 000000000000..eb0decdacacf --- /dev/null +++ b/cmd/clusterctl/cmd/alpha_phase_get_kubeconfig.go @@ -0,0 +1,97 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "io/ioutil" + + "github.com/spf13/cobra" + "k8s.io/klog" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/clusterclient" + "sigs.k8s.io/cluster-api/cmd/clusterctl/phases" +) + +type AlphaPhaseGetKubeconfigOptions struct { + ClusterName string + Kubeconfig string + KubeconfigOutput string + Namespace string + Provider string +} + +var pgko = &AlphaPhaseGetKubeconfigOptions{} + +var alphaPhaseGetKubeconfigCmd = &cobra.Command{ + Use: "get-kubeconfig", + Short: "Get Kubeconfig", + Long: `Get Kubeconfig`, + Run: func(cmd *cobra.Command, args []string) { + if pgko.Kubeconfig == "" { + exitWithHelp(cmd, "Please provide a kubeconfig file.") + } + + if pgko.Provider == "" { + exitWithHelp(cmd, "Please specify a provider.") + } + + if pgko.ClusterName == "" { + exitWithHelp(cmd, "Please specify a cluster name.") + } + + if err := RunAlphaPhaseGetKubeconfig(pgko); err != nil { + klog.Exit(err) + } + }, +} + +func RunAlphaPhaseGetKubeconfig(pgko *AlphaPhaseGetKubeconfigOptions) error { + kubeconfig, err := ioutil.ReadFile(pgko.Kubeconfig) + if err != nil { + return err + } + + clientFactory := clusterclient.NewFactory() + client, err := clientFactory.NewClientFromKubeconfig(string(kubeconfig)) + if err != nil { + return fmt.Errorf("unable to create cluster client: %v", err) + } + + provider, err := getProvider(pgko.Provider) + if err != nil { + return err + } + + if _, err := phases.GetKubeconfig(client, provider, pgko.KubeconfigOutput, pgko.ClusterName, pgko.Namespace); err != nil { + return fmt.Errorf("unable to get kubeconfig: %v", err) + } + + return nil +} + +func init() { + // Required flags + alphaPhaseGetKubeconfigCmd.Flags().StringVarP(&pgko.Kubeconfig, "kubeconfig", "", "", "Path for the kubeconfig file to use") + alphaPhaseGetKubeconfigCmd.Flags().StringVarP(&pgko.ClusterName, "cluster-name", "", "", "Cluster Name") + // TODO: Remove as soon as code allows https://github.com/kubernetes-sigs/cluster-api/issues/157 + alphaPhaseGetKubeconfigCmd.Flags().StringVarP(&pgko.Provider, "provider", "", "", "Which provider deployment logic to use (google/vsphere/azure)") + + // Optional flags + alphaPhaseGetKubeconfigCmd.Flags().StringVarP(&pgko.KubeconfigOutput, "kubeconfig-out", "", "kubeconfig", "Where to output the kubeconfig for the provisioned cluster") + alphaPhaseGetKubeconfigCmd.Flags().StringVarP(&pgko.Namespace, "namespace", "n", "", "Namespace") + alphaPhasesCmd.AddCommand(alphaPhaseGetKubeconfigCmd) +} diff --git a/cmd/clusterctl/cmd/create_cluster.go b/cmd/clusterctl/cmd/create_cluster.go index 2d81ac094e0f..c2aa0c3e3d96 100644 --- a/cmd/clusterctl/cmd/create_cluster.go +++ b/cmd/clusterctl/cmd/create_cluster.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer" "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/bootstrap" "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/clusterclient" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider" clustercommon "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" "sigs.k8s.io/cluster-api/pkg/util" ) @@ -123,14 +124,14 @@ func init() { createCmd.AddCommand(createClusterCmd) } -func getProvider(name string) (clusterdeployer.ProviderDeployer, error) { +func getProvider(name string) (provider.Deployer, error) { provisioner, err := clustercommon.ClusterProvisioner(name) if err != nil { return nil, err } - provider, ok := provisioner.(clusterdeployer.ProviderDeployer) + provider, ok := provisioner.(provider.Deployer) if !ok { - return nil, errors.Errorf("provider for %s does not implement ProviderDeployer interface", name) + return nil, errors.Errorf("provider for %s does not implement provider.Deployer interface", name) } return provider, nil } diff --git a/cmd/clusterctl/phases/BUILD.bazel b/cmd/clusterctl/phases/BUILD.bazel index e3fa4ff1beee..af6dc8711675 100644 --- a/cmd/clusterctl/phases/BUILD.bazel +++ b/cmd/clusterctl/phases/BUILD.bazel @@ -8,13 +8,16 @@ go_library( "applyclusterapicomponents.go", "applymachines.go", "createbootstrapcluster.go", + "getkubeconfig.go", ], importpath = "sigs.k8s.io/cluster-api/cmd/clusterctl/phases", visibility = ["//visibility:public"], deps = [ "//cmd/clusterctl/clusterdeployer/bootstrap:go_default_library", "//cmd/clusterctl/clusterdeployer/clusterclient:go_default_library", + "//cmd/clusterctl/clusterdeployer/provider:go_default_library", "//pkg/apis/cluster/v1alpha1:go_default_library", + "//pkg/util:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/k8s.io/klog:go_default_library", ], diff --git a/cmd/clusterctl/phases/getkubeconfig.go b/cmd/clusterctl/phases/getkubeconfig.go new file mode 100644 index 000000000000..fe5feee11aa2 --- /dev/null +++ b/cmd/clusterctl/phases/getkubeconfig.go @@ -0,0 +1,80 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package phases + +import ( + "fmt" + "io/ioutil" + "os" + "time" + + "k8s.io/klog" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/clusterclient" + "sigs.k8s.io/cluster-api/cmd/clusterctl/clusterdeployer/provider" + clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/cluster-api/pkg/util" +) + +const ( + retryKubeConfigReady = 10 * time.Second + timeoutKubeconfigReady = 20 * time.Minute +) + +// GetKubeconfig returns a kubeconfig for the target cluster +func GetKubeconfig(bootstrapClient clusterclient.Client, provider provider.Deployer, kubeconfigOutput string, clusterName, namespace string) (string, error) { + cluster, controlPlane, _, err := clusterclient.GetClusterAPIObject(bootstrapClient, clusterName, namespace) + if err != nil { + return "", err + } + + klog.V(1).Info("Getting target cluster kubeconfig.") + targetKubeconfig, err := waitForKubeconfigReady(provider, cluster, controlPlane) + if err != nil { + return "", fmt.Errorf("unable to get target cluster kubeconfig: %v", err) + } + + if err := writeKubeconfig(targetKubeconfig, kubeconfigOutput); err != nil { + return "", err + } + + return targetKubeconfig, nil +} + +func waitForKubeconfigReady(provider provider.Deployer, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { + kubeconfig := "" + err := util.PollImmediate(retryKubeConfigReady, timeoutKubeconfigReady, func() (bool, error) { + klog.V(2).Infof("Waiting for kubeconfig on %v to become ready...", machine.Name) + k, err := provider.GetKubeConfig(cluster, machine) + if err != nil { + klog.V(4).Infof("error getting kubeconfig: %v", err) + return false, nil + } + if k == "" { + return false, nil + } + kubeconfig = k + return true, nil + }) + + return kubeconfig, err +} + +func writeKubeconfig(kubeconfig string, kubeconfigOutput string) error { + const fileMode = 0660 + os.Remove(kubeconfigOutput) + return ioutil.WriteFile(kubeconfigOutput, []byte(kubeconfig), fileMode) +}