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 }