sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/client_test.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 "fmt" 22 "testing" 23 "time" 24 25 "github.com/pkg/errors" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/serializer" 28 "k8s.io/apimachinery/pkg/util/wait" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 32 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" 33 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" 34 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" 35 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/tree" 36 yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor" 37 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme" 38 "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" 39 ) 40 41 // TestNewFakeClient is a fake test to document fakeClient usage. 42 func TestNewFakeClient(_ *testing.T) { 43 // create a fake config with a provider named P1 and a variable named var 44 repository1Config := config.NewProvider("p1", "url", clusterctlv1.CoreProviderType) 45 46 ctx := context.Background() 47 48 config1 := newFakeConfig(ctx). 49 WithVar("var", "value"). 50 WithProvider(repository1Config) 51 52 // create a fake repository with some YAML files in it (usually matching the list of providers defined in the config) 53 repository1 := newFakeRepository(ctx, repository1Config, config1). 54 WithPaths("root", "components"). 55 WithDefaultVersion("v1.0"). 56 WithFile("v1.0", "components.yaml", []byte("content")) 57 58 // create a fake cluster, eventually adding some existing runtime objects to it 59 cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "cluster1"}, config1). 60 WithObjs() 61 62 // create a new fakeClient that allows to execute tests on the fake config, the fake repositories and the fake cluster. 63 newFakeClient(context.Background(), config1). 64 WithRepository(repository1). 65 WithCluster(cluster1) 66 } 67 68 type fakeClient struct { 69 configClient config.Client 70 // mapping between kubeconfigPath/context with cluster client 71 clusters map[cluster.Kubeconfig]cluster.Client 72 repositories map[string]repository.Client 73 internalClient *clusterctlClient 74 } 75 76 var _ Client = &fakeClient{} 77 78 func (f fakeClient) GetProvidersConfig() ([]Provider, error) { 79 return f.internalClient.GetProvidersConfig() 80 } 81 82 func (f fakeClient) GetProviderComponents(ctx context.Context, provider string, providerType clusterctlv1.ProviderType, options ComponentsOptions) (Components, error) { 83 return f.internalClient.GetProviderComponents(ctx, provider, providerType, options) 84 } 85 86 func (f fakeClient) GenerateProvider(ctx context.Context, provider string, providerType clusterctlv1.ProviderType, options ComponentsOptions) (Components, error) { 87 return f.internalClient.GenerateProvider(ctx, provider, providerType, options) 88 } 89 90 func (f fakeClient) GetClusterTemplate(ctx context.Context, options GetClusterTemplateOptions) (Template, error) { 91 return f.internalClient.GetClusterTemplate(ctx, options) 92 } 93 94 func (f fakeClient) GetKubeconfig(ctx context.Context, options GetKubeconfigOptions) (string, error) { 95 return f.internalClient.GetKubeconfig(ctx, options) 96 } 97 98 func (f fakeClient) Init(ctx context.Context, options InitOptions) ([]Components, error) { 99 return f.internalClient.Init(ctx, options) 100 } 101 102 func (f fakeClient) InitImages(ctx context.Context, options InitOptions) ([]string, error) { 103 return f.internalClient.InitImages(ctx, options) 104 } 105 106 func (f fakeClient) Delete(ctx context.Context, options DeleteOptions) error { 107 return f.internalClient.Delete(ctx, options) 108 } 109 110 func (f fakeClient) Move(ctx context.Context, options MoveOptions) error { 111 return f.internalClient.Move(ctx, options) 112 } 113 114 func (f fakeClient) PlanUpgrade(ctx context.Context, options PlanUpgradeOptions) ([]UpgradePlan, error) { 115 return f.internalClient.PlanUpgrade(ctx, options) 116 } 117 118 func (f fakeClient) PlanCertManagerUpgrade(ctx context.Context, options PlanUpgradeOptions) (CertManagerUpgradePlan, error) { 119 return f.internalClient.PlanCertManagerUpgrade(ctx, options) 120 } 121 122 func (f fakeClient) ApplyUpgrade(ctx context.Context, options ApplyUpgradeOptions) error { 123 return f.internalClient.ApplyUpgrade(ctx, options) 124 } 125 126 func (f fakeClient) ProcessYAML(ctx context.Context, options ProcessYAMLOptions) (YamlPrinter, error) { 127 return f.internalClient.ProcessYAML(ctx, options) 128 } 129 130 func (f fakeClient) RolloutRestart(ctx context.Context, options RolloutRestartOptions) error { 131 return f.internalClient.RolloutRestart(ctx, options) 132 } 133 134 func (f fakeClient) DescribeCluster(ctx context.Context, options DescribeClusterOptions) (*tree.ObjectTree, error) { 135 return f.internalClient.DescribeCluster(ctx, options) 136 } 137 138 func (f fakeClient) RolloutPause(ctx context.Context, options RolloutPauseOptions) error { 139 return f.internalClient.RolloutPause(ctx, options) 140 } 141 142 func (f fakeClient) RolloutResume(ctx context.Context, options RolloutResumeOptions) error { 143 return f.internalClient.RolloutResume(ctx, options) 144 } 145 146 func (f fakeClient) RolloutUndo(ctx context.Context, options RolloutUndoOptions) error { 147 return f.internalClient.RolloutUndo(ctx, options) 148 } 149 150 func (f fakeClient) TopologyPlan(ctx context.Context, options TopologyPlanOptions) (*cluster.TopologyPlanOutput, error) { 151 return f.internalClient.TopologyPlan(ctx, options) 152 } 153 154 // newFakeClient returns a clusterctl client that allows to execute tests on a set of fake config, fake repositories and fake clusters. 155 // you can use WithCluster and WithRepository to prepare for the test case. 156 func newFakeClient(ctx context.Context, configClient config.Client) *fakeClient { 157 fake := &fakeClient{ 158 clusters: map[cluster.Kubeconfig]cluster.Client{}, 159 repositories: map[string]repository.Client{}, 160 } 161 162 fake.configClient = configClient 163 if fake.configClient == nil { 164 fake.configClient = newFakeConfig(ctx) 165 } 166 167 var clusterClientFactory = func(i ClusterClientFactoryInput) (cluster.Client, error) { 168 // converting the client.Kubeconfig to cluster.Kubeconfig alias 169 k := cluster.Kubeconfig(i.Kubeconfig) 170 if _, ok := fake.clusters[k]; !ok { 171 return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exist", i.Kubeconfig.Path, i.Kubeconfig.Context) 172 } 173 return fake.clusters[k], nil 174 } 175 176 fake.internalClient, _ = newClusterctlClient(ctx, "fake-config", 177 InjectConfig(fake.configClient), 178 InjectClusterClientFactory(clusterClientFactory), 179 InjectRepositoryFactory(func(_ context.Context, input RepositoryClientFactoryInput) (repository.Client, error) { 180 if _, ok := fake.repositories[input.Provider.ManifestLabel()]; !ok { 181 return nil, errors.Errorf("repository for kubeconfig %q does not exist", input.Provider.ManifestLabel()) 182 } 183 return fake.repositories[input.Provider.ManifestLabel()], nil 184 }), 185 ) 186 187 return fake 188 } 189 190 func (f *fakeClient) WithCluster(clusterClient cluster.Client) *fakeClient { 191 input := clusterClient.Kubeconfig() 192 f.clusters[input] = clusterClient 193 return f 194 } 195 196 func (f *fakeClient) WithRepository(repositoryClient repository.Client) *fakeClient { 197 if fc, ok := f.configClient.(fakeConfigClient); ok { 198 fc.WithProvider(repositoryClient) 199 } 200 f.repositories[repositoryClient.ManifestLabel()] = repositoryClient 201 return f 202 } 203 204 // newFakeCluster returns a fakeClusterClient that 205 // internally uses a FakeProxy (based on the controller-runtime FakeClient). 206 // You can use WithObjs to pre-load a set of runtime objects in the cluster. 207 func newFakeCluster(kubeconfig cluster.Kubeconfig, configClient config.Client) *fakeClusterClient { 208 fake := &fakeClusterClient{ 209 kubeconfig: kubeconfig, 210 repositories: map[string]repository.Client{}, 211 certManager: newFakeCertManagerClient(nil, nil), 212 } 213 214 fake.fakeProxy = test.NewFakeProxy() 215 pollImmediateWaiter := func(context.Context, time.Duration, time.Duration, wait.ConditionWithContextFunc) error { 216 return nil 217 } 218 219 fake.internalclient = cluster.New(kubeconfig, configClient, 220 cluster.InjectProxy(fake.fakeProxy), 221 cluster.InjectPollImmediateWaiter(pollImmediateWaiter), 222 cluster.InjectRepositoryFactory(func(_ context.Context, provider config.Provider, _ config.Client, _ ...repository.Option) (repository.Client, error) { 223 if _, ok := fake.repositories[provider.Name()]; !ok { 224 return nil, errors.Errorf("repository for kubeconfig %q does not exist", provider.Name()) 225 } 226 return fake.repositories[provider.Name()], nil 227 }), 228 ) 229 return fake 230 } 231 232 // newFakeCertManagerClient creates a new CertManagerClient 233 // allows the caller to define which images are needed for the manager to run. 234 func newFakeCertManagerClient(imagesReturnImages []string, imagesReturnError error) *fakeCertManagerClient { 235 return &fakeCertManagerClient{ 236 images: imagesReturnImages, 237 imagesError: imagesReturnError, 238 } 239 } 240 241 type fakeCertManagerClient struct { 242 images []string 243 imagesError error 244 certManagerPlan cluster.CertManagerUpgradePlan 245 } 246 247 var _ cluster.CertManagerClient = &fakeCertManagerClient{} 248 249 func (p *fakeCertManagerClient) EnsureInstalled(_ context.Context) error { 250 return nil 251 } 252 253 func (p *fakeCertManagerClient) EnsureLatestVersion(_ context.Context) error { 254 return nil 255 } 256 257 func (p *fakeCertManagerClient) PlanUpgrade(_ context.Context) (cluster.CertManagerUpgradePlan, error) { 258 return p.certManagerPlan, nil 259 } 260 261 func (p *fakeCertManagerClient) Images(_ context.Context) ([]string, error) { 262 return p.images, p.imagesError 263 } 264 265 func (p *fakeCertManagerClient) WithCertManagerPlan(plan CertManagerUpgradePlan) *fakeCertManagerClient { 266 p.certManagerPlan = cluster.CertManagerUpgradePlan(plan) 267 return p 268 } 269 270 type fakeClusterClient struct { 271 kubeconfig cluster.Kubeconfig 272 fakeProxy *test.FakeProxy 273 fakeObjectMover cluster.ObjectMover 274 repositories map[string]repository.Client 275 internalclient cluster.Client 276 certManager cluster.CertManagerClient 277 } 278 279 var _ cluster.Client = &fakeClusterClient{} 280 281 func (f fakeClusterClient) Kubeconfig() cluster.Kubeconfig { 282 return f.kubeconfig 283 } 284 285 func (f fakeClusterClient) Proxy() cluster.Proxy { 286 return f.fakeProxy 287 } 288 289 func (f *fakeClusterClient) CertManager() cluster.CertManagerClient { 290 return f.certManager 291 } 292 293 func (f fakeClusterClient) ProviderComponents() cluster.ComponentsClient { 294 return f.internalclient.ProviderComponents() 295 } 296 297 func (f fakeClusterClient) ProviderInventory() cluster.InventoryClient { 298 return f.internalclient.ProviderInventory() 299 } 300 301 func (f fakeClusterClient) ProviderInstaller() cluster.ProviderInstaller { 302 return f.internalclient.ProviderInstaller() 303 } 304 305 func (f *fakeClusterClient) ObjectMover() cluster.ObjectMover { 306 if f.fakeObjectMover == nil { 307 return f.internalclient.ObjectMover() 308 } 309 return f.fakeObjectMover 310 } 311 312 func (f *fakeClusterClient) ProviderUpgrader() cluster.ProviderUpgrader { 313 return f.internalclient.ProviderUpgrader() 314 } 315 316 func (f *fakeClusterClient) Template() cluster.TemplateClient { 317 return f.internalclient.Template() 318 } 319 320 func (f *fakeClusterClient) WorkloadCluster() cluster.WorkloadCluster { 321 return f.internalclient.WorkloadCluster() 322 } 323 324 func (f *fakeClusterClient) Topology() cluster.TopologyClient { 325 return f.internalclient.Topology() 326 } 327 328 func (f *fakeClusterClient) WithObjs(objs ...client.Object) *fakeClusterClient { 329 f.fakeProxy.WithObjs(objs...) 330 return f 331 } 332 333 func (f *fakeClusterClient) WithProviderInventory(name string, providerType clusterctlv1.ProviderType, version, targetNamespace string) *fakeClusterClient { 334 f.fakeProxy.WithProviderInventory(name, providerType, version, targetNamespace) 335 return f 336 } 337 338 func (f *fakeClusterClient) WithRepository(repositoryClient repository.Client) *fakeClusterClient { 339 f.repositories[repositoryClient.Name()] = repositoryClient 340 return f 341 } 342 343 func (f *fakeClusterClient) WithObjectMover(mover cluster.ObjectMover) *fakeClusterClient { 344 f.fakeObjectMover = mover 345 return f 346 } 347 348 func (f *fakeClusterClient) WithCertManagerClient(client cluster.CertManagerClient) *fakeClusterClient { 349 f.certManager = client 350 return f 351 } 352 353 // newFakeConfig return a fake implementation of the client for low-level config library. 354 // The implementation uses a FakeReader that stores configuration settings in a map; you can use 355 // the WithVar or WithProvider methods to set the map values. 356 func newFakeConfig(ctx context.Context) *fakeConfigClient { 357 fakeReader := test.NewFakeReader() 358 359 client, _ := config.New(ctx, "fake-config", config.InjectReader(fakeReader)) 360 361 return &fakeConfigClient{ 362 fakeReader: fakeReader, 363 internalclient: client, 364 } 365 } 366 367 type fakeConfigClient struct { 368 fakeReader *test.FakeReader 369 internalclient config.Client 370 } 371 372 var _ config.Client = &fakeConfigClient{} 373 374 func (f fakeConfigClient) CertManager() config.CertManagerClient { 375 return f.internalclient.CertManager() 376 } 377 378 func (f fakeConfigClient) Providers() config.ProvidersClient { 379 return f.internalclient.Providers() 380 } 381 382 func (f fakeConfigClient) Variables() config.VariablesClient { 383 return f.internalclient.Variables() 384 } 385 386 func (f fakeConfigClient) ImageMeta() config.ImageMetaClient { 387 return f.internalclient.ImageMeta() 388 } 389 390 func (f *fakeConfigClient) WithVar(key, value string) *fakeConfigClient { 391 f.fakeReader.WithVar(key, value) 392 return f 393 } 394 395 func (f *fakeConfigClient) WithProvider(provider config.Provider) *fakeConfigClient { 396 f.fakeReader.WithProvider(provider.Name(), provider.Type(), provider.URL()) 397 return f 398 } 399 400 // newFakeRepository return a fake implementation of the client for low-level repository library. 401 // The implementation stores configuration settings in a map; you can use 402 // the WithPaths or WithDefaultVersion methods to configure the repository and WithFile to set the map values. 403 func newFakeRepository(ctx context.Context, provider config.Provider, configClient config.Client) *fakeRepositoryClient { 404 fakeRepository := repository.NewMemoryRepository() 405 406 if configClient == nil { 407 configClient = newFakeConfig(ctx) 408 } 409 410 return &fakeRepositoryClient{ 411 Provider: provider, 412 configClient: configClient, 413 fakeRepository: fakeRepository, 414 processor: yaml.NewSimpleProcessor(), 415 } 416 } 417 418 type fakeRepositoryClient struct { 419 config.Provider 420 configClient config.Client 421 fakeRepository *repository.MemoryRepository 422 processor yaml.Processor 423 } 424 425 var _ repository.Client = &fakeRepositoryClient{} 426 427 func (f fakeRepositoryClient) DefaultVersion() string { 428 return f.fakeRepository.DefaultVersion() 429 } 430 431 func (f fakeRepositoryClient) GetVersions(ctx context.Context) ([]string, error) { 432 return f.fakeRepository.GetVersions(ctx) 433 } 434 435 func (f fakeRepositoryClient) Components() repository.ComponentsClient { 436 // use a fakeComponentClient (instead of the internal client used in other fake objects) we can de deterministic on what is returned (e.g. avoid interferences from overrides) 437 return &fakeComponentClient{ 438 provider: f.Provider, 439 fakeRepository: f.fakeRepository, 440 configClient: f.configClient, 441 processor: f.processor, 442 } 443 } 444 445 func (f fakeRepositoryClient) Templates(version string) repository.TemplateClient { 446 // Use a fakeTemplateClient (instead of the internal client used in other fake objects) we can be deterministic on what is returned (e.g. avoid interferences from overrides) 447 return &fakeTemplateClient{ 448 version: version, 449 fakeRepository: f.fakeRepository, 450 configVariablesClient: f.configClient.Variables(), 451 processor: f.processor, 452 } 453 } 454 455 func (f fakeRepositoryClient) ClusterClasses(version string) repository.ClusterClassClient { 456 // Use a fakeTemplateClient (instead of the internal client used in other fake objects) we can be deterministic on what is returned (e.g. avoid interferences from overrides) 457 return &fakeClusterClassClient{ 458 version: version, 459 fakeRepository: f.fakeRepository, 460 configVariablesClient: f.configClient.Variables(), 461 processor: f.processor, 462 } 463 } 464 465 func (f fakeRepositoryClient) Metadata(version string) repository.MetadataClient { 466 // Use a fakeMetadataClient (instead of the internal client used in other fake objects) we can be deterministic on what is returned (e.g. avoid interferences from overrides) 467 return &fakeMetadataClient{ 468 version: version, 469 fakeRepository: f.fakeRepository, 470 } 471 } 472 473 func (f *fakeRepositoryClient) WithPaths(rootPath, componentsPath string) *fakeRepositoryClient { 474 f.fakeRepository.WithPaths(rootPath, componentsPath) 475 return f 476 } 477 478 func (f *fakeRepositoryClient) WithDefaultVersion(version string) *fakeRepositoryClient { 479 f.fakeRepository.WithDefaultVersion(version) 480 return f 481 } 482 483 func (f *fakeRepositoryClient) WithVersions(version ...string) *fakeRepositoryClient { 484 f.fakeRepository.WithVersions(version...) 485 return f 486 } 487 488 func (f *fakeRepositoryClient) WithMetadata(version string, metadata *clusterctlv1.Metadata) *fakeRepositoryClient { 489 f.fakeRepository.WithMetadata(version, metadata) 490 return f 491 } 492 493 func (f *fakeRepositoryClient) WithFile(version, path string, content []byte) *fakeRepositoryClient { 494 f.fakeRepository.WithFile(version, path, content) 495 return f 496 } 497 498 // fakeTemplateClient provides a super simple TemplateClient (e.g. without support for local overrides). 499 type fakeTemplateClient struct { 500 version string 501 fakeRepository *repository.MemoryRepository 502 configVariablesClient config.VariablesClient 503 processor yaml.Processor 504 } 505 506 func (f *fakeTemplateClient) Get(ctx context.Context, flavor, targetNamespace string, skipTemplateProcess bool) (repository.Template, error) { 507 name := "cluster-template" 508 if flavor != "" { 509 name = fmt.Sprintf("%s-%s", name, flavor) 510 } 511 name = fmt.Sprintf("%s.yaml", name) 512 513 content, err := f.fakeRepository.GetFile(ctx, f.version, name) 514 if err != nil { 515 return nil, err 516 } 517 return repository.NewTemplate(repository.TemplateInput{ 518 RawArtifact: content, 519 ConfigVariablesClient: f.configVariablesClient, 520 Processor: f.processor, 521 TargetNamespace: targetNamespace, 522 SkipTemplateProcess: skipTemplateProcess, 523 }) 524 } 525 526 // fakeClusterClassClient provides a super simple TemplateClient (e.g. without support for local overrides). 527 type fakeClusterClassClient struct { 528 version string 529 fakeRepository *repository.MemoryRepository 530 configVariablesClient config.VariablesClient 531 processor yaml.Processor 532 } 533 534 func (f *fakeClusterClassClient) Get(ctx context.Context, class, targetNamespace string, skipTemplateProcess bool) (repository.Template, error) { 535 name := fmt.Sprintf("clusterclass-%s.yaml", class) 536 content, err := f.fakeRepository.GetFile(ctx, f.version, name) 537 if err != nil { 538 return nil, err 539 } 540 return repository.NewTemplate(repository.TemplateInput{ 541 RawArtifact: content, 542 ConfigVariablesClient: f.configVariablesClient, 543 Processor: f.processor, 544 TargetNamespace: targetNamespace, 545 SkipTemplateProcess: skipTemplateProcess, 546 }) 547 } 548 549 // fakeMetadataClient provides a super simple MetadataClient (e.g. without support for local overrides/embedded metadata). 550 type fakeMetadataClient struct { 551 version string 552 fakeRepository *repository.MemoryRepository 553 } 554 555 func (f *fakeMetadataClient) Get(ctx context.Context) (*clusterctlv1.Metadata, error) { 556 content, err := f.fakeRepository.GetFile(ctx, f.version, "metadata.yaml") 557 if err != nil { 558 return nil, err 559 } 560 obj := &clusterctlv1.Metadata{} 561 codecFactory := serializer.NewCodecFactory(scheme.Scheme) 562 563 if err := runtime.DecodeInto(codecFactory.UniversalDecoder(), content, obj); err != nil { 564 return nil, errors.Wrap(err, "error decoding metadata.yaml") 565 } 566 567 return obj, nil 568 } 569 570 // fakeComponentClient provides a super simple ComponentClient (e.g. without support for local overrides). 571 type fakeComponentClient struct { 572 provider config.Provider 573 fakeRepository *repository.MemoryRepository 574 configClient config.Client 575 processor yaml.Processor 576 } 577 578 func (f *fakeComponentClient) Raw(ctx context.Context, options repository.ComponentsOptions) ([]byte, error) { 579 return f.getRawBytes(ctx, &options) 580 } 581 582 func (f *fakeComponentClient) Get(ctx context.Context, options repository.ComponentsOptions) (repository.Components, error) { 583 content, err := f.getRawBytes(ctx, &options) 584 if err != nil { 585 return nil, err 586 } 587 588 return repository.NewComponents( 589 repository.ComponentsInput{ 590 Provider: f.provider, 591 ConfigClient: f.configClient, 592 Processor: f.processor, 593 RawYaml: content, 594 Options: options, 595 }, 596 ) 597 } 598 599 func (f *fakeComponentClient) getRawBytes(ctx context.Context, options *repository.ComponentsOptions) ([]byte, error) { 600 if options.Version == "" { 601 options.Version = f.fakeRepository.DefaultVersion() 602 } 603 path := f.fakeRepository.ComponentsPath() 604 605 return f.fakeRepository.GetFile(ctx, options.Version, path) 606 }