sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/client.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cluster
    18  
    19  import (
    20  	"context"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  	"k8s.io/apimachinery/pkg/util/wait"
    25  
    26  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    27  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
    28  	yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
    29  	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
    30  )
    31  
    32  // Kubeconfig is a type that specifies inputs related to the actual
    33  // kubeconfig.
    34  type Kubeconfig struct {
    35  	// Path to the kubeconfig file
    36  	Path string
    37  	// Specify context within the kubeconfig file. If empty, cluster client
    38  	// will use the current context.
    39  	Context string
    40  }
    41  
    42  // Client is used to interact with a management cluster.
    43  // A management cluster contains following categories of objects:
    44  // - provider components (e.g. the CRDs, controllers, RBAC)
    45  // - provider inventory items (e.g. the list of installed providers/versions)
    46  // - provider objects (e.g. clusters, AWS clusters, machines etc.)
    47  type Client interface {
    48  	// Kubeconfig returns the kubeconfig used to access to a management cluster.
    49  	Kubeconfig() Kubeconfig
    50  
    51  	// Proxy return the Proxy used for operating objects in the management cluster.
    52  	Proxy() Proxy
    53  
    54  	// CertManager returns a CertManagerClient that can be used for
    55  	// operating the cert-manager components in the cluster.
    56  	CertManager() CertManagerClient
    57  
    58  	// ProviderComponents returns a ComponentsClient object that can be used for
    59  	// operating provider components objects in the management cluster (e.g. the CRDs, controllers, RBAC).
    60  	ProviderComponents() ComponentsClient
    61  
    62  	// ProviderInventory returns a InventoryClient object that can be used for
    63  	// operating provider inventory stored in the management cluster (e.g. the list of installed providers/versions).
    64  	ProviderInventory() InventoryClient
    65  
    66  	// ProviderInstaller returns a ProviderInstaller that enforces consistency rules for provider installation,
    67  	// trying to prevent e.g. controllers fighting for objects, inconsistent versions, etc.
    68  	ProviderInstaller() ProviderInstaller
    69  
    70  	// ObjectMover returns an ObjectMover that implements support for moving Cluster API objects (e.g. clusters, AWS clusters, machines, etc.).
    71  	// from one management cluster to another management cluster.
    72  	ObjectMover() ObjectMover
    73  
    74  	// ProviderUpgrader returns a ProviderUpgrader that supports upgrading Cluster API providers.
    75  	ProviderUpgrader() ProviderUpgrader
    76  
    77  	// Template has methods to work with templates stored in the cluster.
    78  	Template() TemplateClient
    79  
    80  	// WorkloadCluster has methods for fetching kubeconfig of workload cluster from management cluster.
    81  	WorkloadCluster() WorkloadCluster
    82  
    83  	// Topology returns a TopologyClient that can be used for performing dry run executions of the topology reconciler.
    84  	Topology() TopologyClient
    85  }
    86  
    87  // PollImmediateWaiter tries a condition func until it returns true, an error, or the timeout is reached.
    88  type PollImmediateWaiter func(ctx context.Context, interval, timeout time.Duration, condition wait.ConditionWithContextFunc) error
    89  
    90  // clusterClient implements Client.
    91  type clusterClient struct {
    92  	configClient            config.Client
    93  	kubeconfig              Kubeconfig
    94  	proxy                   Proxy
    95  	repositoryClientFactory RepositoryClientFactory
    96  	pollImmediateWaiter     PollImmediateWaiter
    97  	processor               yaml.Processor
    98  }
    99  
   100  // RepositoryClientFactory defines a function that returns a new repository.Client.
   101  type RepositoryClientFactory func(ctx context.Context, provider config.Provider, configClient config.Client, options ...repository.Option) (repository.Client, error)
   102  
   103  // ensure clusterClient implements Client.
   104  var _ Client = &clusterClient{}
   105  
   106  func (c *clusterClient) Kubeconfig() Kubeconfig {
   107  	return c.kubeconfig
   108  }
   109  
   110  func (c *clusterClient) Proxy() Proxy {
   111  	return c.proxy
   112  }
   113  
   114  func (c *clusterClient) CertManager() CertManagerClient {
   115  	return newCertManagerClient(c.configClient, c.repositoryClientFactory, c.proxy, c.pollImmediateWaiter)
   116  }
   117  
   118  func (c *clusterClient) ProviderComponents() ComponentsClient {
   119  	return newComponentsClient(c.proxy)
   120  }
   121  
   122  func (c *clusterClient) ProviderInventory() InventoryClient {
   123  	return newInventoryClient(c.proxy, c.pollImmediateWaiter)
   124  }
   125  
   126  func (c *clusterClient) ProviderInstaller() ProviderInstaller {
   127  	return newProviderInstaller(c.configClient, c.repositoryClientFactory, c.proxy, c.ProviderInventory(), c.ProviderComponents())
   128  }
   129  
   130  func (c *clusterClient) ObjectMover() ObjectMover {
   131  	return newObjectMover(c.proxy, c.ProviderInventory())
   132  }
   133  
   134  func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
   135  	return newProviderUpgrader(c.configClient, c.proxy, c.repositoryClientFactory, c.ProviderInventory(), c.ProviderComponents())
   136  }
   137  
   138  func (c *clusterClient) Template() TemplateClient {
   139  	return newTemplateClient(TemplateClientInput{c.proxy, c.configClient, c.processor})
   140  }
   141  
   142  func (c *clusterClient) WorkloadCluster() WorkloadCluster {
   143  	return newWorkloadCluster(c.proxy)
   144  }
   145  
   146  func (c *clusterClient) Topology() TopologyClient {
   147  	return newTopologyClient(c.proxy, c.ProviderInventory())
   148  }
   149  
   150  // Option is a configuration option supplied to New.
   151  type Option func(*clusterClient)
   152  
   153  // InjectProxy allows to override the default proxy used by clusterctl.
   154  func InjectProxy(proxy Proxy) Option {
   155  	return func(c *clusterClient) {
   156  		c.proxy = proxy
   157  	}
   158  }
   159  
   160  // InjectRepositoryFactory allows to override the default factory used for creating
   161  // RepositoryClient objects.
   162  func InjectRepositoryFactory(factory RepositoryClientFactory) Option {
   163  	return func(c *clusterClient) {
   164  		c.repositoryClientFactory = factory
   165  	}
   166  }
   167  
   168  // InjectPollImmediateWaiter allows to override the default PollImmediateWaiter used by clusterctl.
   169  func InjectPollImmediateWaiter(pollImmediateWaiter PollImmediateWaiter) Option {
   170  	return func(c *clusterClient) {
   171  		c.pollImmediateWaiter = pollImmediateWaiter
   172  	}
   173  }
   174  
   175  // InjectYamlProcessor allows you to override the yaml processor that the
   176  // cluster client uses. By default, the SimpleProcessor is used. This is
   177  // true even if a nil processor is injected.
   178  func InjectYamlProcessor(p yaml.Processor) Option {
   179  	return func(c *clusterClient) {
   180  		if p != nil {
   181  			c.processor = p
   182  		}
   183  	}
   184  }
   185  
   186  // New returns a cluster.Client.
   187  func New(kubeconfig Kubeconfig, configClient config.Client, options ...Option) Client {
   188  	return newClusterClient(kubeconfig, configClient, options...)
   189  }
   190  
   191  func newClusterClient(kubeconfig Kubeconfig, configClient config.Client, options ...Option) *clusterClient {
   192  	client := &clusterClient{
   193  		configClient: configClient,
   194  		kubeconfig:   kubeconfig,
   195  		processor:    yaml.NewSimpleProcessor(),
   196  	}
   197  	for _, o := range options {
   198  		o(client)
   199  	}
   200  
   201  	// if there is an injected proxy, use it, otherwise use a default one
   202  	if client.proxy == nil {
   203  		client.proxy = newProxy(client.kubeconfig)
   204  	}
   205  
   206  	// if there is an injected repositoryClientFactory, use it, otherwise use the default one
   207  	if client.repositoryClientFactory == nil {
   208  		client.repositoryClientFactory = repository.New
   209  	}
   210  
   211  	// if there is an injected PollImmediateWaiter, use it, otherwise use the default one
   212  	if client.pollImmediateWaiter == nil {
   213  		client.pollImmediateWaiter = func(ctx context.Context, interval, timeout time.Duration, condition wait.ConditionWithContextFunc) error {
   214  			return wait.PollUntilContextTimeout(ctx, interval, timeout, true, condition)
   215  		}
   216  	}
   217  
   218  	return client
   219  }
   220  
   221  // retryWithExponentialBackoff repeats an operation until it passes or the exponential backoff times out.
   222  func retryWithExponentialBackoff(ctx context.Context, opts wait.Backoff, operation func(ctx context.Context) error) error {
   223  	log := logf.Log
   224  
   225  	i := 0
   226  	err := wait.ExponentialBackoffWithContext(ctx, opts, func(ctx context.Context) (bool, error) {
   227  		i++
   228  		if err := operation(ctx); err != nil {
   229  			if i < opts.Steps {
   230  				log.V(5).Info("Retrying with backoff", "Cause", err.Error())
   231  				return false, nil
   232  			}
   233  			return false, err
   234  		}
   235  		return true, nil
   236  	})
   237  	if err != nil {
   238  		return errors.Wrapf(err, "action failed after %d attempts", i)
   239  	}
   240  	return nil
   241  }
   242  
   243  // newWriteBackoff creates a new API Machinery backoff parameter set suitable for use with clusterctl write operations.
   244  func newWriteBackoff() wait.Backoff {
   245  	// Return a exponential backoff configuration which returns durations for a total time of ~40s.
   246  	// Example: 0, .5s, 1.2s, 2.3s, 4s, 6s, 10s, 16s, 24s, 37s
   247  	// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
   248  	return wait.Backoff{
   249  		Duration: 500 * time.Millisecond,
   250  		Factor:   1.5,
   251  		Steps:    10,
   252  		Jitter:   0.4,
   253  	}
   254  }
   255  
   256  // newConnectBackoff creates a new API Machinery backoff parameter set suitable for use when clusterctl connect to a cluster.
   257  func newConnectBackoff() wait.Backoff {
   258  	// Return a exponential backoff configuration which returns durations for a total time of ~15s.
   259  	// Example: 0, .25s, .6s, 1.2, 2.1s, 3.4s, 5.5s, 8s, 12s
   260  	// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
   261  	return wait.Backoff{
   262  		Duration: 250 * time.Millisecond,
   263  		Factor:   1.5,
   264  		Steps:    9,
   265  		Jitter:   0.1,
   266  	}
   267  }
   268  
   269  // newShortConnectBackoff creates a new API Machinery backoff parameter set suitable for use when clusterctl connect to a cluster.
   270  // Preferred over newConnectBackoff() only when used to perform quick checks to check if a cluster is reachable.
   271  func newShortConnectBackoff() wait.Backoff {
   272  	// Return a exponential backoff configuration which returns durations for a total time of ~5s.
   273  	// Example: 0, .25s, .6s, 1.2, 2.1s, 3.4s, 5.5s.
   274  	// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
   275  	return wait.Backoff{
   276  		Duration: 250 * time.Millisecond,
   277  		Factor:   1.5,
   278  		Steps:    7,
   279  		Jitter:   0.1,
   280  	}
   281  }
   282  
   283  // newReadBackoff creates a new API Machinery backoff parameter set suitable for use with clusterctl read operations.
   284  func newReadBackoff() wait.Backoff {
   285  	// Return a exponential backoff configuration which returns durations for a total time of ~15s.
   286  	// Example: 0, .25s, .6s, 1.2, 2.1s, 3.4s, 5.5s, 8s, 12s
   287  	// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
   288  	return wait.Backoff{
   289  		Duration: 250 * time.Millisecond,
   290  		Factor:   1.5,
   291  		Steps:    9,
   292  		Jitter:   0.1,
   293  	}
   294  }