Skip to content

Commit 4613624

Browse files
authored
Merge pull request #5373 from ykakarap/clusterctl-better-errors
✨clusterctl: generate returns better error message in case there is no management cluster available
2 parents c6d42a7 + a4fe42f commit 4613624

File tree

4 files changed

+92
-30
lines changed

4 files changed

+92
-30
lines changed

cmd/clusterctl/client/cluster/client.go

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ import (
2121
"time"
2222

2323
"github.com/pkg/errors"
24-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2524
"k8s.io/apimachinery/pkg/util/wait"
26-
"k8s.io/client-go/rest"
2725
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
2826
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
2927
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
3028
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
31-
"sigs.k8s.io/controller-runtime/pkg/client"
3229
)
3330

3431
const (
@@ -61,15 +58,15 @@ type Client interface {
6158
// Proxy return the Proxy used for operating objects in the management cluster.
6259
Proxy() Proxy
6360

64-
// CertManager returns a CertManagerClient that can be user for
61+
// CertManager returns a CertManagerClient that can be used for
6562
// operating the cert-manager components in the cluster.
6663
CertManager() CertManagerClient
6764

68-
// ProviderComponents returns a ComponentsClient object that can be user for
65+
// ProviderComponents returns a ComponentsClient object that can be used for
6966
// operating provider components objects in the management cluster (e.g. the CRDs, controllers, RBAC).
7067
ProviderComponents() ComponentsClient
7168

72-
// ProviderInventory returns a InventoryClient object that can be user for
69+
// ProviderInventory returns a InventoryClient object that can be used for
7370
// operating provider inventory stored in the management cluster (e.g. the list of installed providers/versions).
7471
ProviderInventory() InventoryClient
7572

@@ -219,30 +216,6 @@ func newClusterClient(kubeconfig Kubeconfig, configClient config.Client, options
219216
return client
220217
}
221218

222-
// Proxy defines a client proxy interface.
223-
type Proxy interface {
224-
// GetConfig returns the rest.Config
225-
GetConfig() (*rest.Config, error)
226-
227-
// CurrentNamespace returns the namespace from the current context in the kubeconfig file
228-
CurrentNamespace() (string, error)
229-
230-
// ValidateKubernetesVersion returns an error if management cluster version less than minimumKubernetesVersion
231-
ValidateKubernetesVersion() error
232-
233-
// NewClient returns a new controller runtime Client object for working on the management cluster
234-
NewClient() (client.Client, error)
235-
236-
// ListResources returns all the Kubernetes objects with the given labels existing the listed namespaces.
237-
ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error)
238-
239-
// GetContexts returns the list of contexts in kubeconfig which begin with prefix.
240-
GetContexts(prefix string) ([]string, error)
241-
242-
// GetResourceNames returns the list of resource names which begin with prefix.
243-
GetResourceNames(groupVersion, kind string, options []client.ListOption, prefix string) ([]string, error)
244-
}
245-
246219
// retryWithExponentialBackoff repeats an operation until it passes or the exponential backoff times out.
247220
func retryWithExponentialBackoff(opts wait.Backoff, operation func() error) error {
248221
log := logf.Log
@@ -291,6 +264,20 @@ func newConnectBackoff() wait.Backoff {
291264
}
292265
}
293266

267+
// newShortConnectBackoff creates a new API Machinery backoff parameter set suitable for use when clusterctl connect to a cluster.
268+
// Preferred over newConnectBackoff() only when used to perform quick checks to check if a cluster is reachable.
269+
func newShortConnectBackoff() wait.Backoff {
270+
// Return a exponential backoff configuration which returns durations for a total time of ~5s.
271+
// Example: 0, .25s, .6s, 1.2, 2.1s, 3.4s, 5.5s.
272+
// Jitter is added as a random fraction of the duration multiplied by the jitter factor.
273+
return wait.Backoff{
274+
Duration: 250 * time.Millisecond,
275+
Factor: 1.5,
276+
Steps: 7,
277+
Jitter: 0.1,
278+
}
279+
}
280+
294281
// newReadBackoff creates a new API Machinery backoff parameter set suitable for use with clusterctl read operations.
295282
func newReadBackoff() wait.Backoff {
296283
// Return a exponential backoff configuration which returns durations for a total time of ~15s.

cmd/clusterctl/client/cluster/proxy.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,33 @@ var (
3939
localScheme = scheme.Scheme
4040
)
4141

42+
// Proxy defines a client proxy interface.
43+
type Proxy interface {
44+
// GetConfig returns the rest.Config
45+
GetConfig() (*rest.Config, error)
46+
47+
// CurrentNamespace returns the namespace from the current context in the kubeconfig file.
48+
CurrentNamespace() (string, error)
49+
50+
// ValidateKubernetesVersion returns an error if management cluster version less than minimumKubernetesVersion.
51+
ValidateKubernetesVersion() error
52+
53+
// NewClient returns a new controller runtime Client object for working on the management cluster.
54+
NewClient() (client.Client, error)
55+
56+
// CheckClusterAvailable checks if a a cluster is available and reachable.
57+
CheckClusterAvailable() error
58+
59+
// ListResources returns all the Kubernetes objects with the given labels existing the listed namespaces.
60+
ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error)
61+
62+
// GetContexts returns the list of contexts in kubeconfig which begin with prefix.
63+
GetContexts(prefix string) ([]string, error)
64+
65+
// GetResourceNames returns the list of resource names which begin with prefix.
66+
GetResourceNames(groupVersion, kind string, options []client.ListOption, prefix string) ([]string, error)
67+
}
68+
4269
type proxy struct {
4370
kubeconfig Kubeconfig
4471
timeout time.Duration
@@ -150,6 +177,27 @@ func (k *proxy) NewClient() (client.Client, error) {
150177
return c, nil
151178
}
152179

180+
func (k *proxy) CheckClusterAvailable() error {
181+
// Check if the cluster is available by creating a client to the cluster.
182+
// If creating the client times out and never established we assume that
183+
// the cluster does not exist or is not reachable.
184+
// For the purposes of clusterctl operations non-existent clusters and
185+
// non-reachable clusters can be treated as the same.
186+
config, err := k.GetConfig()
187+
if err != nil {
188+
return err
189+
}
190+
191+
connectBackoff := newShortConnectBackoff()
192+
if err := retryWithExponentialBackoff(connectBackoff, func() error {
193+
_, err := client.New(config, client.Options{Scheme: localScheme})
194+
return err
195+
}); err != nil {
196+
return err
197+
}
198+
return nil
199+
}
200+
153201
func (k *proxy) ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error) {
154202
cs, err := k.newClientSet()
155203
if err != nil {

cmd/clusterctl/client/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ func (c *clusterctlClient) GetClusterTemplate(options GetClusterTemplateOptions)
224224

225225
// If the option specifying the targetNamespace is empty, try to detect it.
226226
if options.TargetNamespace == "" {
227+
if err := clusterClient.Proxy().CheckClusterAvailable(); err != nil {
228+
return nil, errors.Wrap(err, "management cluster not available. Cannot auto-discover target namespace. Please specify a target namespace")
229+
}
227230
currentNamespace, err := clusterClient.Proxy().CurrentNamespace()
228231
if err != nil {
229232
return nil, err
@@ -273,6 +276,9 @@ func (c *clusterctlClient) getTemplateFromRepository(cluster cluster.Client, opt
273276
provider := source.InfrastructureProvider
274277
ensureCustomResourceDefinitions := false
275278
if provider == "" {
279+
if err := cluster.Proxy().CheckClusterAvailable(); err != nil {
280+
return nil, errors.Wrap(err, "management cluster not available. Cannot auto-discover default infrastructure provider. Please specify an infrastructure provider")
281+
}
276282
// ensure the custom resource definitions required by clusterctl are in place
277283
if err := cluster.ProviderInventory().EnsureCustomResourceDefinitions(); err != nil {
278284
return nil, errors.Wrapf(err, "provider custom resource definitions (CRDs) are not installed")
@@ -298,6 +304,9 @@ func (c *clusterctlClient) getTemplateFromRepository(cluster cluster.Client, opt
298304

299305
// If the version of the infrastructure provider to get templates from is empty, try to detect it.
300306
if version == "" {
307+
if err := cluster.Proxy().CheckClusterAvailable(); err != nil {
308+
return nil, errors.Wrapf(err, "management cluster not available. Cannot auto-discover version for the provider %q automatically. Please specify a version", name)
309+
}
301310
// ensure the custom resource definitions required by clusterctl are in place (if not already done)
302311
if !ensureCustomResourceDefinitions {
303312
if err := cluster.ProviderInventory().EnsureCustomResourceDefinitions(); err != nil {

cmd/clusterctl/internal/test/fake_proxy.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ limitations under the License.
1717
package test
1818

1919
import (
20+
"errors"
21+
2022
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2123
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2224
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2325
"k8s.io/apimachinery/pkg/runtime"
2426
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2527
"k8s.io/client-go/rest"
28+
"k8s.io/utils/pointer"
2629
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2730
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
2831
fakebootstrap "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/bootstrap"
@@ -39,6 +42,7 @@ type FakeProxy struct {
3942
cs client.Client
4043
namespace string
4144
objs []client.Object
45+
available *bool
4246
}
4347

4448
var (
@@ -79,6 +83,15 @@ func (f *FakeProxy) NewClient() (client.Client, error) {
7983
return f.cs, nil
8084
}
8185

86+
func (f *FakeProxy) CheckClusterAvailable() error {
87+
// default to considering the cluster as available unless explicitly set to be
88+
// unavailable.
89+
if f.available == nil || *f.available {
90+
return nil
91+
}
92+
return errors.New("cluster is not available")
93+
}
94+
8295
// ListResources returns all the resources known by the FakeProxy.
8396
func (f *FakeProxy) ListResources(labels map[string]string, namespaces ...string) ([]unstructured.Unstructured, error) {
8497
var ret []unstructured.Unstructured //nolint
@@ -185,6 +198,11 @@ func (f *FakeProxy) WithFakeCAPISetup() *FakeProxy {
185198
return f
186199
}
187200

201+
func (f *FakeProxy) WithClusterAvailable(available bool) *FakeProxy {
202+
f.available = pointer.Bool(available)
203+
return f
204+
}
205+
188206
// FakeCAPISetupObjects return required objects in order to make kubeadm pass checks
189207
// ensuring that management cluster has a proper release of Cluster API installed.
190208
func FakeCAPISetupObjects() []client.Object {

0 commit comments

Comments
 (0)