Skip to content

Commit

Permalink
Merge pull request #2856 from wfernandes/clusterctl-kubeconfig-context
Browse files Browse the repository at this point in the history
✨Support specific kubeconfig context in clusterctl
  • Loading branch information
k8s-ci-robot authored Apr 16, 2020
2 parents f38fd7d + 02d07a2 commit c96fd59
Show file tree
Hide file tree
Showing 24 changed files with 715 additions and 169 deletions.
3 changes: 3 additions & 0 deletions cmd/clusterctl/client/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ type Template repository.Template

// UpgradePlan defines a list of possible upgrade targets for a management group.
type UpgradePlan cluster.UpgradePlan

// Kubeconfig is a type that specifies inputs related to the actual kubeconfig.
type Kubeconfig cluster.Kubeconfig
55 changes: 5 additions & 50 deletions cmd/clusterctl/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,6 @@ import (
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
)

// InitOptions carries the options supported by Init.
type InitOptions struct {
// Kubeconfig file to use for accessing the management cluster. If empty, default discovery rules apply.
Kubeconfig string

// CoreProvider version (e.g. cluster-api:v0.3.0) to add to the management cluster. If unspecified, the
// cluster-api core provider's latest release is used.
CoreProvider string

// BootstrapProviders and versions (e.g. kubeadm:v0.3.0) to add to the management cluster.
// If unspecified, the kubeadm bootstrap provider's latest release is used.
BootstrapProviders []string

// InfrastructureProviders and versions (e.g. aws:v0.5.0) to add to the management cluster.
InfrastructureProviders []string

// ControlPlaneProviders and versions (e.g. kubeadm:v0.3.0) to add to the management cluster.
// If unspecified, the kubeadm control plane provider latest release is used.
ControlPlaneProviders []string

// TargetNamespace defines the namespace where the providers should be deployed. If unspecified, each provider
// will be installed in a provider's default namespace.
TargetNamespace string

// WatchingNamespace defines the namespace the providers should watch to reconcile Cluster API objects.
// If unspecified, the providers watches for Cluster API objects across all namespaces.
WatchingNamespace string

// LogUsageInstructions instructs the init command to print the usage instructions in case of first run.
LogUsageInstructions bool
}

// MoveOptions carries the options supported by move.
type MoveOptions struct {
// FromKubeconfig defines the kubeconfig file to use for accessing the source management cluster. If empty,
// default rules for kubeconfig discovery will be used.
FromKubeconfig string

// ToKubeconfig defines the path to the kubeconfig file to use for accessing the target management cluster.
ToKubeconfig string

// Namespace where the objects describing the workload cluster exists. If unspecified, the current
// namespace will be used.
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.
Expand Down Expand Up @@ -111,7 +65,7 @@ type clusterctlClient struct {
}

type RepositoryClientFactory func(config.Provider) (repository.Client, error)
type ClusterClientFactory func(string) (cluster.Client, error)
type ClusterClientFactory func(Kubeconfig) (cluster.Client, error)

// Ensure clusterctlClient implements Client.
var _ Client = &clusterctlClient{}
Expand Down Expand Up @@ -177,9 +131,10 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
}

// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
func defaultClusterFactory(configClient config.Client) func(kubeconfig string) (cluster.Client, error) {
return func(kubeconfig string) (cluster.Client, error) {
return cluster.New(kubeconfig, configClient), nil
func defaultClusterFactory(configClient config.Client) func(kubeconfig Kubeconfig) (cluster.Client, error) {
return func(kubeconfig Kubeconfig) (cluster.Client, error) {
// Kubeconfig is a type alias to cluster.Kubeconfig
return cluster.New(cluster.Kubeconfig(kubeconfig), configClient), nil
}
}

Expand Down
47 changes: 30 additions & 17 deletions cmd/clusterctl/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestNewFakeClient(t *testing.T) {
WithFile("v1.0", "components.yaml", []byte("content"))

// create a fake cluster, eventually adding some existing runtime objects to it
cluster1 := newFakeCluster("cluster1", config1).
cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "cluster1"}, config1).
WithObjs()

// create a new fakeClient that allows to execute tests on the fake config, the fake repositories and the fake cluster.
Expand All @@ -59,8 +59,9 @@ func TestNewFakeClient(t *testing.T) {
}

type fakeClient struct {
configClient config.Client
clusters map[string]cluster.Client
configClient config.Client
// mapping between kubeconfigPath/context with cluster client
clusters map[cluster.Kubeconfig]cluster.Client
repositories map[string]repository.Client
internalClient *clusterctlClient
}
Expand Down Expand Up @@ -108,7 +109,7 @@ func (f fakeClient) ApplyUpgrade(options ApplyUpgradeOptions) error {
func newFakeClient(configClient config.Client) *fakeClient {

fake := &fakeClient{
clusters: map[string]cluster.Client{},
clusters: map[cluster.Kubeconfig]cluster.Client{},
repositories: map[string]repository.Client{},
}

Expand All @@ -117,11 +118,13 @@ func newFakeClient(configClient config.Client) *fakeClient {
fake.configClient = newFakeConfig()
}

var clusterClientFactory = func(kubeconfig string) (cluster.Client, error) {
if _, ok := fake.clusters[kubeconfig]; !ok {
return nil, errors.Errorf("Cluster for kubeconfig %q does not exists.", kubeconfig)
var clusterClientFactory = func(i Kubeconfig) (cluster.Client, error) {
// converting the client.Kubeconfig to cluster.Kubeconfig alias
k := cluster.Kubeconfig(i)
if _, ok := fake.clusters[k]; !ok {
return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exists.", i.Path, i.Context)
}
return fake.clusters[kubeconfig], nil
return fake.clusters[k], nil
}

fake.internalClient, _ = newClusterctlClient("fake-config",
Expand All @@ -139,7 +142,8 @@ func newFakeClient(configClient config.Client) *fakeClient {
}

func (f *fakeClient) WithCluster(clusterClient cluster.Client) *fakeClient {
f.clusters[clusterClient.Kubeconfig()] = clusterClient
input := clusterClient.Kubeconfig()
f.clusters[input] = clusterClient
return f
}

Expand All @@ -154,7 +158,7 @@ func (f *fakeClient) WithRepository(repositoryClient repository.Client) *fakeCli
// newFakeCluster returns a fakeClusterClient that
// internally uses a FakeProxy (based on the controller-runtime FakeClient).
// You can use WithObjs to pre-load a set of runtime objects in the cluster.
func newFakeCluster(kubeconfig string, configClient config.Client) *fakeClusterClient {
func newFakeCluster(kubeconfig cluster.Kubeconfig, configClient config.Client) *fakeClusterClient {
fake := &fakeClusterClient{
kubeconfig: kubeconfig,
repositories: map[string]repository.Client{},
Expand All @@ -165,7 +169,7 @@ func newFakeCluster(kubeconfig string, configClient config.Client) *fakeClusterC
return nil
}

fake.internalclient = cluster.New("", configClient,
fake.internalclient = cluster.New(kubeconfig, configClient,
cluster.InjectProxy(fake.fakeProxy),
cluster.InjectPollImmediateWaiter(pollImmediateWaiter),
cluster.InjectRepositoryFactory(func(provider config.Provider, configClient config.Client, options ...repository.Option) (repository.Client, error) {
Expand Down Expand Up @@ -195,15 +199,16 @@ func (p *fakeCertManagerClient) Images() ([]string, error) {
}

type fakeClusterClient struct {
kubeconfig string
fakeProxy *test.FakeProxy
repositories map[string]repository.Client
internalclient cluster.Client
kubeconfig cluster.Kubeconfig
fakeProxy *test.FakeProxy
fakeObjectMover cluster.ObjectMover
repositories map[string]repository.Client
internalclient cluster.Client
}

var _ cluster.Client = &fakeClusterClient{}

func (f fakeClusterClient) Kubeconfig() string {
func (f fakeClusterClient) Kubeconfig() cluster.Kubeconfig {
return f.kubeconfig
}

Expand All @@ -228,7 +233,10 @@ func (f fakeClusterClient) ProviderInstaller() cluster.ProviderInstaller {
}

func (f *fakeClusterClient) ObjectMover() cluster.ObjectMover {
return f.internalclient.ObjectMover()
if f.fakeObjectMover == nil {
return f.internalclient.ObjectMover()
}
return f.fakeObjectMover
}

func (f *fakeClusterClient) ProviderUpgrader() cluster.ProviderUpgrader {
Expand All @@ -254,6 +262,11 @@ func (f *fakeClusterClient) WithRepository(repositoryClient repository.Client) *
return f
}

func (f *fakeClusterClient) WithObjectMover(mover cluster.ObjectMover) *fakeClusterClient {
f.fakeObjectMover = mover
return f
}

// newFakeConfig return a fake implementation of the client for low-level config library.
// The implementation uses a FakeReader that stores configuration settings in a map; you can use
// the WithVar or WithProvider methods to set the map values.
Expand Down
31 changes: 21 additions & 10 deletions cmd/clusterctl/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand All @@ -38,14 +38,24 @@ var (
ctx = context.TODO()
)

// Kubeconfig is a type that specifies inputs related to the actual
// kubeconfig.
type Kubeconfig struct {
// Path to the kubeconfig file
Path string
// Specify context within the kubeconfig file. If empty, cluster client
// will use the current context.
Context string
}

// Client is used to interact with a management cluster.
// A management cluster contains following categories of objects:
// - provider components (e.g. the CRDs, controllers, RBAC)
// - provider inventory items (e.g. the list of installed providers/versions)
// - provider objects (e.g. clusters, AWS clusters, machines etc.)
type Client interface {
// Kubeconfig return the path to kubeconfig used to access to a management cluster.
Kubeconfig() string
// Kubeconfig returns the kubeconfig used to access to a management cluster.
Kubeconfig() Kubeconfig

// Proxy return the Proxy used for operating objects in the management cluster.
Proxy() Proxy
Expand Down Expand Up @@ -83,7 +93,7 @@ type PollImmediateWaiter func(interval, timeout time.Duration, condition wait.Co
// clusterClient implements Client.
type clusterClient struct {
configClient config.Client
kubeconfig string
kubeconfig Kubeconfig
proxy Proxy
repositoryClientFactory RepositoryClientFactory
pollImmediateWaiter PollImmediateWaiter
Expand All @@ -94,7 +104,7 @@ type RepositoryClientFactory func(provider config.Provider, configClient config.
// ensure clusterClient implements Client.
var _ Client = &clusterClient{}

func (c *clusterClient) Kubeconfig() string {
func (c *clusterClient) Kubeconfig() Kubeconfig {
return c.kubeconfig
}

Expand Down Expand Up @@ -156,11 +166,11 @@ func InjectPollImmediateWaiter(pollImmediateWaiter PollImmediateWaiter) Option {
}

// New returns a cluster.Client.
func New(kubeconfig string, configClient config.Client, options ...Option) Client {
func New(kubeconfig Kubeconfig, configClient config.Client, options ...Option) Client {
return newClusterClient(kubeconfig, configClient, options...)
}

func newClusterClient(kubeconfig string, configClient config.Client, options ...Option) *clusterClient {
func newClusterClient(kubeconfig Kubeconfig, configClient config.Client, options ...Option) *clusterClient {
client := &clusterClient{
configClient: configClient,
kubeconfig: kubeconfig,
Expand All @@ -171,7 +181,7 @@ func newClusterClient(kubeconfig string, configClient config.Client, options ...

// if there is an injected proxy, use it, otherwise use a default one
if client.proxy == nil {
client.proxy = newProxy(kubeconfig)
client.proxy = newProxy(client.kubeconfig)
}

// if there is an injected repositoryClientFactory, use it, otherwise use the default one
Expand All @@ -188,6 +198,9 @@ func newClusterClient(kubeconfig string, configClient config.Client, options ...
}

type Proxy interface {
// GetConfig returns the rest.Config
GetConfig() (*rest.Config, error)

// CurrentNamespace returns the namespace from the current context in the kubeconfig file
CurrentNamespace() (string, error)

Expand All @@ -201,8 +214,6 @@ type Proxy interface {
ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error)
}

var _ Proxy = &test.FakeProxy{}

// retryWithExponentialBackoff repeats an operation until it passes or the exponential backoff times out.
func retryWithExponentialBackoff(opts wait.Backoff, operation func() error) error { //nolint:unparam
log := logf.Log
Expand Down
Loading

0 comments on commit c96fd59

Please sign in to comment.