sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/config.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  	"io"
    22  	"strconv"
    23  
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/util/version"
    26  	"k8s.io/utils/ptr"
    27  
    28  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
    30  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
    31  	yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
    32  )
    33  
    34  func (c *clusterctlClient) GetProvidersConfig() ([]Provider, error) {
    35  	r, err := c.configClient.Providers().List()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	// Provider is an alias for config.Provider; this makes the conversion
    41  	rr := make([]Provider, len(r))
    42  	for i, provider := range r {
    43  		rr[i] = provider
    44  	}
    45  
    46  	return rr, nil
    47  }
    48  
    49  func (c *clusterctlClient) GetProviderComponents(ctx context.Context, provider string, providerType clusterctlv1.ProviderType, options ComponentsOptions) (Components, error) {
    50  	components, err := c.getComponentsByName(ctx, provider, providerType, repository.ComponentsOptions(options))
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	return components, nil
    56  }
    57  
    58  // ReaderSourceOptions define the options to be used when reading a template
    59  // from an arbitrary reader.
    60  type ReaderSourceOptions struct {
    61  	Reader io.Reader
    62  }
    63  
    64  // ProcessYAMLOptions are the options supported by ProcessYAML.
    65  type ProcessYAMLOptions struct {
    66  	ReaderSource *ReaderSourceOptions
    67  	// URLSource to be used for reading the template
    68  	URLSource *URLSourceOptions
    69  
    70  	// SkipTemplateProcess return the list of variables expected by the template
    71  	// without executing any further processing.
    72  	SkipTemplateProcess bool
    73  }
    74  
    75  func (c *clusterctlClient) ProcessYAML(ctx context.Context, options ProcessYAMLOptions) (YamlPrinter, error) {
    76  	if options.ReaderSource != nil {
    77  		// NOTE: Beware of potentially reading in large files all at once
    78  		// since this is inefficient and increases memory utilziation.
    79  		content, err := io.ReadAll(options.ReaderSource.Reader)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		return repository.NewTemplate(repository.TemplateInput{
    84  			RawArtifact:           content,
    85  			ConfigVariablesClient: c.configClient.Variables(),
    86  			Processor:             yaml.NewSimpleProcessor(),
    87  			TargetNamespace:       "",
    88  			SkipTemplateProcess:   options.SkipTemplateProcess,
    89  		})
    90  	}
    91  
    92  	// Technically we do not need to connect to the cluster. However, we are
    93  	// leveraging the template client which exposes GetFromURL() is available
    94  	// on the cluster client so we create a cluster client with default
    95  	// configs to access it.
    96  	clstr, err := c.clusterClientFactory(
    97  		ClusterClientFactoryInput{
    98  			// use the default kubeconfig
    99  			Kubeconfig: Kubeconfig{},
   100  		},
   101  	)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	if options.URLSource != nil {
   107  		return c.getTemplateFromURL(ctx, clstr, *options.URLSource, "", options.SkipTemplateProcess)
   108  	}
   109  
   110  	return nil, errors.New("unable to read custom template. Please specify a template source")
   111  }
   112  
   113  // GetClusterTemplateOptions carries the options supported by GetClusterTemplate.
   114  type GetClusterTemplateOptions struct {
   115  	// Kubeconfig defines the kubeconfig to use for accessing the management cluster. If empty,
   116  	// default rules for kubeconfig discovery will be used.
   117  	Kubeconfig Kubeconfig
   118  
   119  	// ProviderRepositorySource to be used for reading the workload cluster template from a provider repository;
   120  	// only one template source can be used at time; if not other source will be set, a ProviderRepositorySource
   121  	// will be generated inferring values from the cluster.
   122  	ProviderRepositorySource *ProviderRepositorySourceOptions
   123  
   124  	// URLSource to be used for reading the workload cluster template; only one template source can be used at time.
   125  	URLSource *URLSourceOptions
   126  
   127  	// ConfigMapSource to be used for reading the workload cluster template; only one template source can be used at time.
   128  	ConfigMapSource *ConfigMapSourceOptions
   129  
   130  	// TargetNamespace where the objects describing the workload cluster should be deployed. If unspecified,
   131  	// the current namespace will be used.
   132  	TargetNamespace string
   133  
   134  	// ClusterName to be used for the workload cluster.
   135  	ClusterName string
   136  
   137  	// KubernetesVersion to use for the workload cluster. If unspecified, the value from os env variables
   138  	// or the $XDG_CONFIG_HOME/cluster-api/clusterctl.yaml or .cluster-api/clusterctl.yaml config file will be used.
   139  	KubernetesVersion string
   140  
   141  	// ControlPlaneMachineCount defines the number of control plane machines to be added to the workload cluster.
   142  	// It can be set through the cli flag, CONTROL_PLANE_MACHINE_COUNT environment variable or will default to 1
   143  	ControlPlaneMachineCount *int64
   144  
   145  	// WorkerMachineCount defines number of worker machines to be added to the workload cluster.
   146  	// It can be set through the cli flag, WORKER_MACHINE_COUNT environment variable or will default to 0
   147  	WorkerMachineCount *int64
   148  
   149  	// ListVariablesOnly sets the GetClusterTemplate method to return the list of variables expected by the template
   150  	// without executing any further processing.
   151  	ListVariablesOnly bool
   152  
   153  	// YamlProcessor defines the yaml processor to use for the cluster
   154  	// template processing. If not defined, SimpleProcessor will be used.
   155  	YamlProcessor Processor
   156  }
   157  
   158  // numSources return the number of template sources currently set on a GetClusterTemplateOptions.
   159  func (o *GetClusterTemplateOptions) numSources() int {
   160  	numSources := 0
   161  	if o.ProviderRepositorySource != nil {
   162  		numSources++
   163  	}
   164  	if o.ConfigMapSource != nil {
   165  		numSources++
   166  	}
   167  	if o.URLSource != nil {
   168  		numSources++
   169  	}
   170  	return numSources
   171  }
   172  
   173  // ProviderRepositorySourceOptions defines the options to be used when reading a workload cluster template
   174  // from a provider repository.
   175  type ProviderRepositorySourceOptions struct {
   176  	// InfrastructureProvider to read the workload cluster template from. If unspecified, the default
   177  	// infrastructure provider will be used if no other sources are specified.
   178  	InfrastructureProvider string
   179  
   180  	// Flavor defines The workload cluster template variant to be used when reading from the infrastructure
   181  	// provider repository. If unspecified, the default cluster template will be used.
   182  	Flavor string
   183  }
   184  
   185  // URLSourceOptions defines the options to be used when reading a workload cluster template from an URL.
   186  type URLSourceOptions struct {
   187  	// URL to read the workload cluster template from.
   188  	URL string
   189  }
   190  
   191  // DefaultCustomTemplateConfigMapKey  where the workload cluster template is hosted.
   192  const DefaultCustomTemplateConfigMapKey = "template"
   193  
   194  // ConfigMapSourceOptions defines the options to be used when reading a workload cluster template from a ConfigMap.
   195  type ConfigMapSourceOptions struct {
   196  	// Namespace where the ConfigMap exists. If unspecified, the current namespace will be used.
   197  	Namespace string
   198  
   199  	// Name to read the workload cluster template from.
   200  	Name string
   201  
   202  	// DataKey where the workload cluster template is hosted. If unspecified, the
   203  	// DefaultCustomTemplateConfigMapKey will be used.
   204  	DataKey string
   205  }
   206  
   207  func (c *clusterctlClient) GetClusterTemplate(ctx context.Context, options GetClusterTemplateOptions) (Template, error) {
   208  	// Checks that no more than on source is set
   209  	numsSource := options.numSources()
   210  	if numsSource > 1 {
   211  		return nil, errors.New("invalid cluster template source: only one template can be used at time")
   212  	}
   213  
   214  	// If no source is set, defaults to using an empty ProviderRepositorySource so values will be
   215  	// inferred from the cluster inventory.
   216  	if numsSource == 0 {
   217  		options.ProviderRepositorySource = &ProviderRepositorySourceOptions{}
   218  	}
   219  
   220  	// Gets  the client for the current management cluster
   221  	clusterClient, err := c.clusterClientFactory(ClusterClientFactoryInput{options.Kubeconfig, options.YamlProcessor})
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	// If the option specifying the targetNamespace is empty, try to detect it.
   227  	if options.TargetNamespace == "" {
   228  		if err := clusterClient.Proxy().CheckClusterAvailable(ctx); err != nil {
   229  			return nil, errors.Wrap(err, "management cluster not available. Cannot auto-discover target namespace. Please specify a target namespace")
   230  		}
   231  		currentNamespace, err := clusterClient.Proxy().CurrentNamespace()
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  		if currentNamespace == "" {
   236  			return nil, errors.New("failed to identify the current namespace. Please specify a target namespace")
   237  		}
   238  		options.TargetNamespace = currentNamespace
   239  	}
   240  
   241  	// Inject some of the templateOptions into the configClient so they can be consumed as a variables from the template.
   242  	if err := c.templateOptionsToVariables(options); err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	// Gets the workload cluster template from the selected source
   247  	if options.ProviderRepositorySource != nil {
   248  		// Ensure this command only runs against management clusters with the current Cluster API contract.
   249  		// NOTE: This command tolerates also not existing cluster (Kubeconfig.Path=="") or clusters not yet initialized in order to allow
   250  		// users to dry-run the command and take a look at what the cluster will look like; in both scenarios, it is required
   251  		// to pass provider:version given that auto-discovery can't work without a provider inventory installed in a cluster.
   252  		if options.Kubeconfig.Path != "" {
   253  			if err := clusterClient.ProviderInventory().CheckCAPIContract(ctx, cluster.AllowCAPINotInstalled{}); err != nil {
   254  				return nil, err
   255  			}
   256  		}
   257  		return c.getTemplateFromRepository(ctx, clusterClient, options)
   258  	}
   259  	if options.ConfigMapSource != nil {
   260  		return c.getTemplateFromConfigMap(ctx, clusterClient, *options.ConfigMapSource, options.TargetNamespace, options.ListVariablesOnly)
   261  	}
   262  	if options.URLSource != nil {
   263  		return c.getTemplateFromURL(ctx, clusterClient, *options.URLSource, options.TargetNamespace, options.ListVariablesOnly)
   264  	}
   265  
   266  	return nil, errors.New("unable to read custom template. Please specify a template source")
   267  }
   268  
   269  // getTemplateFromRepository returns a workload cluster template from a provider repository.
   270  func (c *clusterctlClient) getTemplateFromRepository(ctx context.Context, cluster cluster.Client, options GetClusterTemplateOptions) (Template, error) {
   271  	source := *options.ProviderRepositorySource
   272  	targetNamespace := options.TargetNamespace
   273  	listVariablesOnly := options.ListVariablesOnly
   274  	processor := options.YamlProcessor
   275  
   276  	// If the option specifying the name of the infrastructure provider to get templates from is empty, try to detect it.
   277  	provider := source.InfrastructureProvider
   278  	ensureCustomResourceDefinitions := false
   279  	if provider == "" {
   280  		if err := cluster.Proxy().CheckClusterAvailable(ctx); err != nil {
   281  			return nil, errors.Wrap(err, "management cluster not available. Cannot auto-discover default infrastructure provider. Please specify an infrastructure provider")
   282  		}
   283  		// ensure the custom resource definitions required by clusterctl are in place
   284  		if err := cluster.ProviderInventory().EnsureCustomResourceDefinitions(ctx); err != nil {
   285  			return nil, errors.Wrapf(err, "provider custom resource definitions (CRDs) are not installed")
   286  		}
   287  		ensureCustomResourceDefinitions = true
   288  
   289  		defaultProviderName, err := cluster.ProviderInventory().GetDefaultProviderName(ctx, clusterctlv1.InfrastructureProviderType)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  
   294  		if defaultProviderName == "" {
   295  			return nil, errors.New("failed to identify the default infrastructure provider. Please specify an infrastructure provider")
   296  		}
   297  		provider = defaultProviderName
   298  	}
   299  
   300  	// parse the abbreviated syntax for name[:version]
   301  	name, version, err := parseProviderName(provider)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	// If the version of the infrastructure provider to get templates from is empty, try to detect it.
   307  	if version == "" {
   308  		if err := cluster.Proxy().CheckClusterAvailable(ctx); err != nil {
   309  			return nil, errors.Wrapf(err, "management cluster not available. Cannot auto-discover version for the provider %q automatically. Please specify a version", name)
   310  		}
   311  		// ensure the custom resource definitions required by clusterctl are in place (if not already done)
   312  		if !ensureCustomResourceDefinitions {
   313  			if err := cluster.ProviderInventory().EnsureCustomResourceDefinitions(ctx); err != nil {
   314  				return nil, errors.Wrapf(err, "failed to identify the default version for the provider %q. Please specify a version", name)
   315  			}
   316  		}
   317  
   318  		inventoryVersion, err := cluster.ProviderInventory().GetProviderVersion(ctx, name, clusterctlv1.InfrastructureProviderType)
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  
   323  		if inventoryVersion == "" {
   324  			return nil, errors.Errorf("Unable to identify version for the provider %q automatically. Please specify a version", name)
   325  		}
   326  		version = inventoryVersion
   327  	}
   328  
   329  	// Get the template from the template repository.
   330  	providerConfig, err := c.configClient.Providers().Get(name, clusterctlv1.InfrastructureProviderType)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	repo, err := c.repositoryClientFactory(ctx, RepositoryClientFactoryInput{Provider: providerConfig, Processor: processor})
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	template, err := repo.Templates(version).Get(ctx, source.Flavor, targetNamespace, listVariablesOnly)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  
   345  	clusterClassClient := repo.ClusterClasses(version)
   346  
   347  	template, err = addClusterClassIfMissing(ctx, template, clusterClassClient, cluster, targetNamespace, listVariablesOnly)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	return template, nil
   353  }
   354  
   355  // getTemplateFromConfigMap returns a workload cluster template from a ConfigMap.
   356  func (c *clusterctlClient) getTemplateFromConfigMap(ctx context.Context, cluster cluster.Client, source ConfigMapSourceOptions, targetNamespace string, listVariablesOnly bool) (Template, error) {
   357  	// If the option specifying the configMapNamespace is empty, default it to the current namespace.
   358  	if source.Namespace == "" {
   359  		currentNamespace, err := cluster.Proxy().CurrentNamespace()
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  		source.Namespace = currentNamespace
   364  	}
   365  
   366  	// If the option specifying the configMapDataKey is empty, default it.
   367  	if source.DataKey == "" {
   368  		source.DataKey = DefaultCustomTemplateConfigMapKey
   369  	}
   370  
   371  	return cluster.Template().GetFromConfigMap(ctx, source.Namespace, source.Name, source.DataKey, targetNamespace, listVariablesOnly)
   372  }
   373  
   374  // getTemplateFromURL returns a workload cluster template from an URL.
   375  func (c *clusterctlClient) getTemplateFromURL(ctx context.Context, cluster cluster.Client, source URLSourceOptions, targetNamespace string, listVariablesOnly bool) (Template, error) {
   376  	return cluster.Template().GetFromURL(ctx, source.URL, targetNamespace, listVariablesOnly)
   377  }
   378  
   379  // templateOptionsToVariables injects some of the templateOptions to the configClient so they can be consumed as a variables from the template.
   380  func (c *clusterctlClient) templateOptionsToVariables(options GetClusterTemplateOptions) error {
   381  	// the TargetNamespace, if valid, can be used in templates using the ${ NAMESPACE } variable.
   382  	if err := validateDNS1123Label(options.TargetNamespace); err != nil {
   383  		return errors.Wrapf(err, "invalid target-namespace")
   384  	}
   385  	c.configClient.Variables().Set("NAMESPACE", options.TargetNamespace)
   386  
   387  	// the ClusterName, if valid, can be used in templates using the ${ CLUSTER_NAME } variable.
   388  	if err := validateDNS1123Domanin(options.ClusterName); err != nil {
   389  		return errors.Wrapf(err, "invalid cluster name")
   390  	}
   391  	c.configClient.Variables().Set("CLUSTER_NAME", options.ClusterName)
   392  
   393  	// the KubernetesVersion, if valid, can be used in templates using the ${ KUBERNETES_VERSION } variable.
   394  	// NB. in case the KubernetesVersion from the templateOptions is empty, we are not setting any values so the
   395  	// configClient is going to search into os env variables/the clusterctl config file as a fallback options.
   396  	if options.KubernetesVersion != "" {
   397  		if _, err := version.ParseSemantic(options.KubernetesVersion); err != nil {
   398  			return errors.Errorf("invalid KubernetesVersion. Please use a semantic version number")
   399  		}
   400  		c.configClient.Variables().Set("KUBERNETES_VERSION", options.KubernetesVersion)
   401  	}
   402  
   403  	// the ControlPlaneMachineCount, if valid, can be used in templates using the ${ CONTROL_PLANE_MACHINE_COUNT } variable.
   404  	if options.ControlPlaneMachineCount == nil {
   405  		// Check if set through env variable and default to 1 otherwise
   406  		if v, err := c.configClient.Variables().Get("CONTROL_PLANE_MACHINE_COUNT"); err != nil {
   407  			options.ControlPlaneMachineCount = ptr.To[int64](1)
   408  		} else {
   409  			i, err := strconv.ParseInt(v, 10, 64)
   410  			if err != nil {
   411  				return errors.Errorf("invalid value for CONTROL_PLANE_MACHINE_COUNT set")
   412  			}
   413  			options.ControlPlaneMachineCount = &i
   414  		}
   415  	}
   416  	if *options.ControlPlaneMachineCount < 1 {
   417  		return errors.Errorf("invalid ControlPlaneMachineCount. Please use a number greater than or equal to 1")
   418  	}
   419  	c.configClient.Variables().Set("CONTROL_PLANE_MACHINE_COUNT", strconv.FormatInt(*options.ControlPlaneMachineCount, 10))
   420  
   421  	// the WorkerMachineCount, if valid, can be used in templates using the ${ WORKER_MACHINE_COUNT } variable.
   422  	if options.WorkerMachineCount == nil {
   423  		// Check if set through env variable and default to 0 otherwise
   424  		if v, err := c.configClient.Variables().Get("WORKER_MACHINE_COUNT"); err != nil {
   425  			options.WorkerMachineCount = ptr.To[int64](0)
   426  		} else {
   427  			i, err := strconv.ParseInt(v, 10, 64)
   428  			if err != nil {
   429  				return errors.Errorf("invalid value for WORKER_MACHINE_COUNT set")
   430  			}
   431  			options.WorkerMachineCount = &i
   432  		}
   433  	}
   434  	if *options.WorkerMachineCount < 0 {
   435  		return errors.Errorf("invalid WorkerMachineCount. Please use a number greater than or equal to 0")
   436  	}
   437  	c.configClient.Variables().Set("WORKER_MACHINE_COUNT", strconv.FormatInt(*options.WorkerMachineCount, 10))
   438  
   439  	return nil
   440  }