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 }