From bb8a58c9dfe49b2062b9e035168e2b247411bebb Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Sun, 26 Jan 2020 13:57:53 +0100 Subject: [PATCH] clusterctl add move cmd --- cmd/clusterctl/cmd/move.go | 15 ++++++ cmd/clusterctl/pkg/client/client.go | 24 +++++++--- cmd/clusterctl/pkg/client/client_test.go | 4 ++ cmd/clusterctl/pkg/client/move.go | 47 +++++++++++++++++++ docs/book/src/clusterctl/commands/move.md | 35 ++++++++++++++ docs/book/src/clusterctl/provider-contract.md | 38 ++++++++++++++- 6 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 cmd/clusterctl/pkg/client/move.go diff --git a/cmd/clusterctl/cmd/move.go b/cmd/clusterctl/cmd/move.go index 10fe2cacca4e..acb26038dd21 100644 --- a/cmd/clusterctl/cmd/move.go +++ b/cmd/clusterctl/cmd/move.go @@ -17,8 +17,11 @@ limitations under the License. package cmd import ( + "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client" ) type moveOptions struct { @@ -59,5 +62,17 @@ func init() { } func runMove() error { + c, err := client.New(cfgFile) + if err != nil { + return err + } + fmt.Println("Performing move...") + if err := c.Move(client.MoveOptions{ + FromKubeconfig: mo.fromKubeconfig, + ToKubeconfig: mo.toKubeconfig, + Namespace: mo.namespace, + }); err != nil { + return err + } return nil } diff --git a/cmd/clusterctl/pkg/client/client.go b/cmd/clusterctl/pkg/client/client.go index cecef1adfea9..b1ca872d8b1f 100644 --- a/cmd/clusterctl/pkg/client/client.go +++ b/cmd/clusterctl/pkg/client/client.go @@ -54,7 +54,14 @@ type DeleteOptions struct { Providers []string } -// Client is exposes the clusterctl high-level client library +// MoveOptions carries the options supported by move. +type MoveOptions struct { + FromKubeconfig string + ToKubeconfig string + Namespace string +} + +// Client is exposes the clusterctl high-level client library. type Client interface { // GetProvidersConfig returns the list of providers configured for this instance of clusterctl. GetProvidersConfig() ([]Provider, error) @@ -70,6 +77,9 @@ type Client interface { // Delete deletes providers from a management cluster. Delete(options DeleteOptions) error + + // Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster. + Move(options MoveOptions) error } // clusterctlClient implements Client. @@ -85,7 +95,7 @@ type ClusterClientFactory func(string) (cluster.Client, error) // Ensure clusterctlClient implements Client. var _ Client = &clusterctlClient{} -// NewOptions carries the options supported by New +// NewOptions carries the options supported by New. type NewOptions struct { injectConfig config.Client injectRepositoryFactory RepositoryClientFactory @@ -130,7 +140,7 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err } // if there is an injected config, use it, otherwise use the default one - // provided by the config low level library + // provided by the config low level library. configClient := cfg.injectConfig if configClient == nil { c, err := config.New(path) @@ -140,13 +150,13 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err configClient = c } - // if there is an injected RepositoryFactory, use it, otherwise use a default one + // if there is an injected RepositoryFactory, use it, otherwise use a default one. repositoryClientFactory := cfg.injectRepositoryFactory if repositoryClientFactory == nil { repositoryClientFactory = defaultRepositoryFactory(configClient) } - // if there is an injected ClusterFactory, use it, otherwise use a default one + // if there is an injected ClusterFactory, use it, otherwise use a default one. clusterClientFactory := cfg.injectClusterFactory if clusterClientFactory == nil { clusterClientFactory = defaultClusterFactory() @@ -159,14 +169,14 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err }, nil } -// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library +// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library. func defaultClusterFactory() func(kubeconfig string) (cluster.Client, error) { return func(kubeconfig string) (cluster.Client, error) { return cluster.New(kubeconfig, cluster.Options{}), nil } } -// defaultRepositoryFactory is a RepositoryClientFactory func the uses the default client provided by the repository low level library +// defaultRepositoryFactory is a RepositoryClientFactory func the uses the default client provided by the repository low level library. func defaultRepositoryFactory(configClient config.Client) func(providerConfig config.Provider) (repository.Client, error) { return func(providerConfig config.Provider) (repository.Client, error) { return repository.New(providerConfig, configClient.Variables(), repository.Options{}) diff --git a/cmd/clusterctl/pkg/client/client_test.go b/cmd/clusterctl/pkg/client/client_test.go index 8a207f37003e..03945b28367a 100644 --- a/cmd/clusterctl/pkg/client/client_test.go +++ b/cmd/clusterctl/pkg/client/client_test.go @@ -82,6 +82,10 @@ func (f fakeClient) Delete(options DeleteOptions) error { return f.internalClient.Delete(options) } +func (f fakeClient) Move(options MoveOptions) error { + return f.internalClient.Move(options) +} + // newFakeClient returns a clusterctl client that allows to execute tests on a set of fake config, fake repositories and fake clusters. // you can use WithCluster and WithRepository to prepare for the test case. func newFakeClient(configClient config.Client) *fakeClient { diff --git a/cmd/clusterctl/pkg/client/move.go b/cmd/clusterctl/pkg/client/move.go new file mode 100644 index 000000000000..f0250a79481a --- /dev/null +++ b/cmd/clusterctl/pkg/client/move.go @@ -0,0 +1,47 @@ +/* +Copyright 2020 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 client + +func (c *clusterctlClient) Move(options MoveOptions) error { + // Get the client for interacting with the source management cluster. + fromCluster, err := c.clusterClientFactory(options.FromKubeconfig) + if err != nil { + return err + } + + // Ensures the custom resource definitions required by clusterctl are in place. + if err := fromCluster.ProviderInventory().EnsureCustomResourceDefinitions(); err != nil { + return err + } + + // Get the client for interacting with the target management cluster. + toCluster, err := c.clusterClientFactory(options.ToKubeconfig) + if err != nil { + return err + } + + // Ensures the custom resource definitions required by clusterctl are in place + if err := toCluster.ProviderInventory().EnsureCustomResourceDefinitions(); err != nil { + return err + } + + if err := fromCluster.ObjectMover().Move(options.Namespace, toCluster); err != nil { + return err + } + + return nil +} diff --git a/docs/book/src/clusterctl/commands/move.md b/docs/book/src/clusterctl/commands/move.md index 8932ad55ca72..69354e1d43b0 100644 --- a/docs/book/src/clusterctl/commands/move.md +++ b/docs/book/src/clusterctl/commands/move.md @@ -1 +1,36 @@ # clusterctl move + +The `clusterctl move` command allows to move the Cluster API objects defining workload clusters, like e.g. Cluster, Machines, +MachineDeployments, etc. from one management cluster to another management cluster. + + + +You can use: + +```shell +clusterctl move --to-kubeconfig="path-to-target-kubeconfig.yaml" +``` + +To move all the Cluster API objects objects in the source management cluster; in case if you want to move only the +Cluster API objects defined in a specific namespace, you can use the `--namespace` flag. + + diff --git a/docs/book/src/clusterctl/provider-contract.md b/docs/book/src/clusterctl/provider-contract.md index 86c0c4470d1d..90f75e4b4b2b 100644 --- a/docs/book/src/clusterctl/provider-contract.md +++ b/docs/book/src/clusterctl/provider-contract.md @@ -163,6 +163,25 @@ Templates writers should use the common variables to ensure consistency across p Additionally, value of the command argument to `clusterctl config cluster ` (`` in this case), will be applied to every occurrence of the `${ CLUSTER_NAME }` variable. +## OwnerReferences chain + +Each provider is responsible to ensure that all the providers resources (like e.g. `VSphereCluster`, `VSphereMachine`, `VSphereVM` etc. +for the `vsphere` provider) MUST have a `Metadata.OwnerReferences` entry that links directly or indirectly to a `Cluster` object. + +Please note that all the provider specific resources that are referenced by the Cluster API core objects will get the `OwnerReference` +sets by the Cluster API core controllers, e.g.: + +- The Cluster controller ensures that all the objects referenced in `Cluster.Spec.InfrastructureRef` get an `OwnerReference` + that links directly to the corresponding `Cluster`. +- The Machine controller ensures that all the objects referenced in `Machine.Spec.InfrastructureRef` get an `OwnerReference` + that links to the corresponding `Machine`, and the `Machine` is linked to the `Cluster` through its own `OwnerReference` chain. + +That means that, practically speaking, provider implementers are responsible for ensuring that the `OwnerReference`s +are set only for objects that are not directly referenced by Cluster API core objects, e.g.: + +- All the `VSphereVM` instances should get an `OwnerReference` that links to the corresponding `VSphereMachine`, and the `VSphereMachine` + is linked to the `Cluster` through its own `OwnerReference` chain. + ## Additional notes ### Components YAML transformations @@ -199,10 +218,25 @@ If, for any reason, the provider authors/YAML designers decide not to comply wit The provider authors/YAML designers should be aware that it is their responsibility to ensure the proper functioning of all the `clusterctl` features both in single tenancy or multi-tenancy scenarios and/or document known limitations. -### Move constraints +### Move -WIP +Provider authors should be aware that `clusterctl move` command implements a discovery mechanism that considers: + +* All the objects of Kind defined in one of the CRDs installed by clusterctl using `clusterctl init`. +* `Secret` and `ConfigMap` objects. +* the `OwnerReference` chain of the above objects. + +`clusterctl move` does NOT consider any objects: +* Not included in the set of objects defined above. +* Included in the set of objects defined above, but not directly or indirectly to a `Cluster` object through the `OwnerReference` chain. + +If moving some of excluded object is required, the provider authors should create documentation describing the +the exact move sequence to be executed by the user. + +Additionally, provider authors should be aware that `clusterctl move` assumes all the provider's Controllers respect the +`Cluster.Spec.Paused` field introduced in the v1alpha3 Cluster API specification. + ### Adopt WIP