sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/init.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 client
    18  
    19  import (
    20  	"context"
    21  	"sort"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  
    26  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    27  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
    28  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
    30  	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
    31  )
    32  
    33  // NoopProvider determines if a provider passed in should behave as a no-op.
    34  const NoopProvider = "-"
    35  
    36  // InitOptions carries the options supported by Init.
    37  type InitOptions struct {
    38  	// Kubeconfig defines the kubeconfig to use for accessing the management cluster. If empty,
    39  	// default rules for kubeconfig discovery will be used.
    40  	Kubeconfig Kubeconfig
    41  
    42  	// CoreProvider version (e.g. cluster-api:v1.1.5) to add to the management cluster. If unspecified, the
    43  	// cluster-api core provider's latest release is used.
    44  	CoreProvider string
    45  
    46  	// BootstrapProviders and versions (e.g. kubeadm:v1.1.5) to add to the management cluster.
    47  	// If unspecified, the kubeadm bootstrap provider's latest release is used.
    48  	BootstrapProviders []string
    49  
    50  	// InfrastructureProviders and versions (e.g. aws:v0.5.0) to add to the management cluster.
    51  	InfrastructureProviders []string
    52  
    53  	// ControlPlaneProviders and versions (e.g. kubeadm:v1.1.5) to add to the management cluster.
    54  	// If unspecified, the kubeadm control plane provider latest release is used.
    55  	ControlPlaneProviders []string
    56  
    57  	// IPAMProviders and versions (e.g. infoblox:v0.0.1) to add to the management cluster.
    58  	IPAMProviders []string
    59  
    60  	// RuntimeExtensionProviders and versions (e.g. test:v0.0.1) to add to the management cluster.
    61  	RuntimeExtensionProviders []string
    62  
    63  	// AddonProviders and versions (e.g. helm:v0.1.0) to add to the management cluster.
    64  	AddonProviders []string
    65  
    66  	// TargetNamespace defines the namespace where the providers should be deployed. If unspecified, each provider
    67  	// will be installed in a provider's default namespace.
    68  	TargetNamespace string
    69  
    70  	// LogUsageInstructions instructs the init command to print the usage instructions in case of first run.
    71  	LogUsageInstructions bool
    72  
    73  	// WaitProviders instructs the init command to wait till the providers are installed.
    74  	WaitProviders bool
    75  
    76  	// WaitProviderTimeout sets the timeout per provider wait installation
    77  	WaitProviderTimeout time.Duration
    78  
    79  	// SkipTemplateProcess allows for skipping the call to the template processor, including also variable replacement in the component YAML.
    80  	// NOTE this works only if the rawYaml is a valid yaml by itself, like e.g when using envsubst/the simple processor.
    81  	skipTemplateProcess bool
    82  
    83  	// IgnoreValidationErrors allows for skipping the validation of provider installs.
    84  	// NOTE this should only be used for development
    85  	IgnoreValidationErrors bool
    86  
    87  	// allowMissingProviderCRD is used to allow for a missing provider CRD when listing images.
    88  	// It is set to false to enforce that provider CRD is available when performing the standard init operation.
    89  	allowMissingProviderCRD bool
    90  }
    91  
    92  // Init initializes a management cluster by adding the requested list of providers.
    93  func (c *clusterctlClient) Init(ctx context.Context, options InitOptions) ([]Components, error) {
    94  	log := logf.Log
    95  
    96  	// Default WaitProviderTimeout as we cannot rely on defaulting in the CLI
    97  	// when clusterctl is used as a library.
    98  	if options.WaitProviderTimeout.Nanoseconds() == 0 {
    99  		options.WaitProviderTimeout = time.Duration(5*60) * time.Second
   100  	}
   101  
   102  	// gets access to the management cluster
   103  	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	// ensure the custom resource definitions required by clusterctl are in place
   109  	if err := clusterClient.ProviderInventory().EnsureCustomResourceDefinitions(ctx); err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// Ensure this command only runs against v1beta1 management clusters
   114  	if err := clusterClient.ProviderInventory().CheckCAPIContract(ctx, cluster.AllowCAPINotInstalled{}); err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// checks if the cluster already contains a Core provider.
   119  	// if not we consider this the first time init is executed, and thus we enforce the installation of a core provider,
   120  	// a bootstrap provider and a control-plane provider (if not already explicitly requested by the user)
   121  	log.Info("Fetching providers")
   122  	firstRun := c.addDefaultProviders(ctx, clusterClient, &options)
   123  
   124  	// create an installer service, add the requested providers to the install queue and then perform validation
   125  	// of the target state of the management cluster before starting the installation.
   126  	installer, err := c.setupInstaller(ctx, clusterClient, options)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Before installing the providers, validates the management cluster resulting by the planned installation. The following checks are performed:
   132  	// - There should be only one instance of the same provider.
   133  	// - All the providers must support the same API Version of Cluster API (contract)
   134  	// - All provider CRDs that are referenced in core Cluster API CRDs must comply with the CRD naming scheme,
   135  	//   otherwise a warning is logged.
   136  	if err := installer.Validate(ctx); err != nil {
   137  		if !options.IgnoreValidationErrors {
   138  			return nil, err
   139  		}
   140  		log.Error(err, "Ignoring validation errors")
   141  	}
   142  
   143  	// Before installing the providers, ensure the cert-manager Webhook is in place.
   144  	certManager := clusterClient.CertManager()
   145  	if err := certManager.EnsureInstalled(ctx); err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	installOpts := cluster.InstallOptions{
   150  		WaitProviders:       options.WaitProviders,
   151  		WaitProviderTimeout: options.WaitProviderTimeout,
   152  	}
   153  	components, err := installer.Install(ctx, installOpts)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	// If this is the firstRun, then log the usage instructions.
   159  	if firstRun && options.LogUsageInstructions {
   160  		log.Info("")
   161  		log.Info("Your management cluster has been initialized successfully!")
   162  		log.Info("")
   163  		log.Info("You can now create your first workload cluster by running the following:")
   164  		log.Info("")
   165  		log.Info("  clusterctl generate cluster [name] --kubernetes-version [version] | kubectl apply -f -")
   166  		log.Info("")
   167  	}
   168  
   169  	// Components is an alias for repository.Components; this makes the conversion from the two types
   170  	aliasComponents := make([]Components, len(components))
   171  	for i, components := range components {
   172  		aliasComponents[i] = components
   173  	}
   174  	return aliasComponents, nil
   175  }
   176  
   177  // InitImages returns the list of images required for init.
   178  func (c *clusterctlClient) InitImages(ctx context.Context, options InitOptions) ([]string, error) {
   179  	// gets access to the management cluster
   180  	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	// Ensure this command only runs against empty management clusters or v1beta1 management clusters.
   186  	if err := clusterClient.ProviderInventory().CheckCAPIContract(ctx, cluster.AllowCAPINotInstalled{}); err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	// checks if the cluster already contains a Core provider.
   191  	// if not we consider this the first time init is executed, and thus we enforce the installation of a core provider,
   192  	// a bootstrap provider and a control-plane provider (if not already explicitly requested by the user)
   193  	c.addDefaultProviders(ctx, clusterClient, &options)
   194  
   195  	// skip variable parsing when listing images
   196  	options.skipTemplateProcess = true
   197  
   198  	options.allowMissingProviderCRD = true
   199  
   200  	// create an installer service, add the requested providers to the install queue and then perform validation
   201  	// of the target state of the management cluster before starting the installation.
   202  	installer, err := c.setupInstaller(ctx, clusterClient, options)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	// Gets the list of container images required for the cert-manager (if not already installed).
   208  	certManager := clusterClient.CertManager()
   209  	images, err := certManager.Images(ctx)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	// Appends the list of container images required for the selected providers.
   215  	images = append(images, installer.Images()...)
   216  
   217  	sort.Strings(images)
   218  	return images, nil
   219  }
   220  
   221  func (c *clusterctlClient) setupInstaller(ctx context.Context, cluster cluster.Client, options InitOptions) (cluster.ProviderInstaller, error) {
   222  	installer := cluster.ProviderInstaller()
   223  
   224  	providerList := &clusterctlv1.ProviderList{}
   225  
   226  	addOptions := addToInstallerOptions{
   227  		installer:           installer,
   228  		targetNamespace:     options.TargetNamespace,
   229  		skipTemplateProcess: options.skipTemplateProcess,
   230  		providerList:        providerList,
   231  	}
   232  
   233  	if !options.allowMissingProviderCRD {
   234  		providerList, err := cluster.ProviderInventory().List(ctx)
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  
   239  		addOptions.providerList = providerList
   240  	}
   241  
   242  	if options.CoreProvider != "" {
   243  		if err := c.addToInstaller(ctx, addOptions, clusterctlv1.CoreProviderType, options.CoreProvider); err != nil {
   244  			return nil, err
   245  		}
   246  	}
   247  
   248  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.BootstrapProviderType, options.BootstrapProviders...); err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.ControlPlaneProviderType, options.ControlPlaneProviders...); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.InfrastructureProviderType, options.InfrastructureProviders...); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.IPAMProviderType, options.IPAMProviders...); err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.RuntimeExtensionProviderType, options.RuntimeExtensionProviders...); err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	if err := c.addToInstaller(ctx, addOptions, clusterctlv1.AddonProviderType, options.AddonProviders...); err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	return installer, nil
   273  }
   274  
   275  func (c *clusterctlClient) addDefaultProviders(ctx context.Context, cluster cluster.Client, options *InitOptions) bool {
   276  	firstRun := false
   277  	// Check if there is already a core provider installed in the cluster
   278  	// Nb. we are ignoring the error so this operation can support listing images even if there is no an existing management cluster;
   279  	// in case there is no an existing management cluster, we assume there are no core providers installed in the cluster.
   280  	currentCoreProvider, _ := cluster.ProviderInventory().GetDefaultProviderName(ctx, clusterctlv1.CoreProviderType)
   281  
   282  	// If there are no core providers installed in the cluster, consider this a first run and add default providers to the list
   283  	// of providers to be installed.
   284  	if currentCoreProvider == "" {
   285  		firstRun = true
   286  		if options.CoreProvider == "" {
   287  			options.CoreProvider = config.ClusterAPIProviderName
   288  		}
   289  		if len(options.BootstrapProviders) == 0 {
   290  			options.BootstrapProviders = append(options.BootstrapProviders, config.KubeadmBootstrapProviderName)
   291  		}
   292  		if len(options.ControlPlaneProviders) == 0 {
   293  			options.ControlPlaneProviders = append(options.ControlPlaneProviders, config.KubeadmControlPlaneProviderName)
   294  		}
   295  	}
   296  	return firstRun
   297  }
   298  
   299  type addToInstallerOptions struct {
   300  	installer           cluster.ProviderInstaller
   301  	targetNamespace     string
   302  	skipTemplateProcess bool
   303  	providerList        *clusterctlv1.ProviderList
   304  }
   305  
   306  // addToInstaller adds the components to the install queue and checks that the actual provider type match the target group.
   307  func (c *clusterctlClient) addToInstaller(ctx context.Context, options addToInstallerOptions, providerType clusterctlv1.ProviderType, providers ...string) error {
   308  	for _, provider := range providers {
   309  		// It is possible to opt-out from automatic installation of bootstrap/control-plane providers using '-' as a provider name (NoopProvider).
   310  		if provider == NoopProvider {
   311  			if providerType == clusterctlv1.CoreProviderType {
   312  				return errors.New("the '-' value can not be used for the core provider")
   313  			}
   314  			continue
   315  		}
   316  		componentsOptions := repository.ComponentsOptions{
   317  			TargetNamespace:     options.targetNamespace,
   318  			SkipTemplateProcess: options.skipTemplateProcess,
   319  		}
   320  		components, err := c.getComponentsByName(ctx, provider, providerType, componentsOptions)
   321  		if err != nil {
   322  			return errors.Wrapf(err, "failed to get provider components for the %q provider", provider)
   323  		}
   324  
   325  		if components.Type() != providerType {
   326  			return errors.Errorf("can't use %q provider as an %q, it is a %q", provider, providerType, components.Type())
   327  		}
   328  
   329  		// If a provider of the same name, type and version already exists in the Cluster skip adding it to the installer.
   330  		matchingProviders := options.providerList.FilterByProviderNameNamespaceTypeVersion(
   331  			components.Name(),
   332  			components.TargetNamespace(),
   333  			components.Type(),
   334  			components.Version(),
   335  		)
   336  		if len(matchingProviders) != 0 {
   337  			continue
   338  		}
   339  
   340  		options.installer.Add(components)
   341  	}
   342  	return nil
   343  }