sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/installer_test.go (about)

     1  /*
     2  Copyright 2020 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 cluster
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  
    27  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    28  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    29  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
    30  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
    31  )
    32  
    33  func Test_providerInstaller_Validate(t *testing.T) {
    34  	fakeReader := test.NewFakeReader().
    35  		WithProvider("cluster-api", clusterctlv1.CoreProviderType, "https://somewhere.com").
    36  		WithProvider("infra1", clusterctlv1.InfrastructureProviderType, "https://somewhere.com").
    37  		WithProvider("infra2", clusterctlv1.InfrastructureProviderType, "https://somewhere.com")
    38  
    39  	repositoryMap := map[string]repository.Repository{
    40  		"cluster-api": repository.NewMemoryRepository().
    41  			WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0").
    42  			WithMetadata("v0.9.0", &clusterctlv1.Metadata{
    43  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    44  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    45  				},
    46  			}).
    47  			WithMetadata("v1.0.0", &clusterctlv1.Metadata{
    48  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    49  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    50  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    51  				},
    52  			}).
    53  			WithMetadata("v2.0.0", &clusterctlv1.Metadata{
    54  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    55  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    56  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    57  					{Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported},
    58  				},
    59  			}),
    60  		"infrastructure-infra1": repository.NewMemoryRepository().
    61  			WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0").
    62  			WithMetadata("v0.9.0", &clusterctlv1.Metadata{
    63  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    64  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    65  				},
    66  			}).
    67  			WithMetadata("v1.0.0", &clusterctlv1.Metadata{
    68  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    69  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    70  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    71  				},
    72  			}).
    73  			WithMetadata("v2.0.0", &clusterctlv1.Metadata{
    74  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    75  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    76  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    77  					{Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported},
    78  				},
    79  			}),
    80  		"infrastructure-infra2": repository.NewMemoryRepository().
    81  			WithVersions("v0.9.0", "v1.0.0", "v1.0.1", "v2.0.0").
    82  			WithMetadata("v0.9.0", &clusterctlv1.Metadata{
    83  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    84  					{Major: 0, Minor: 9, Contract: test.PreviousCAPIContractNotSupported},
    85  				},
    86  			}).
    87  			WithMetadata("v1.0.0", &clusterctlv1.Metadata{
    88  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    89  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    90  				},
    91  			}).
    92  			WithMetadata("v2.0.0", &clusterctlv1.Metadata{
    93  				ReleaseSeries: []clusterctlv1.ReleaseSeries{
    94  					{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
    95  					{Major: 2, Minor: 0, Contract: test.NextCAPIContractNotSupported},
    96  				},
    97  			}),
    98  	}
    99  
   100  	type fields struct {
   101  		proxy        Proxy
   102  		installQueue []repository.Components
   103  	}
   104  	tests := []struct {
   105  		name    string
   106  		fields  fields
   107  		wantErr bool
   108  	}{
   109  		{
   110  			name: "install core/current contract + infra1/current contract on an empty cluster",
   111  			fields: fields{
   112  				proxy: test.NewFakeProxy(), // empty cluster
   113  				installQueue: []repository.Components{
   114  					newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
   115  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra1-system"),
   116  				},
   117  			},
   118  			wantErr: false,
   119  		},
   120  		{
   121  			name: "install infra2/current contract on a cluster already initialized with core/current contract + infra1/current contract",
   122  			fields: fields{
   123  				proxy: test.NewFakeProxy().
   124  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system").
   125  					WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra1-system"),
   126  				installQueue: []repository.Components{
   127  					newFakeComponents("infra2", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra2-system"),
   128  				},
   129  			},
   130  			wantErr: false,
   131  		},
   132  		{
   133  			name: "install another instance of infra1/current contract on a cluster already initialized with core/current contract + infra1/current contract",
   134  			fields: fields{
   135  				proxy: test.NewFakeProxy().
   136  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system").
   137  					WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "ns1"),
   138  				installQueue: []repository.Components{
   139  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "ns2"),
   140  				},
   141  			},
   142  			wantErr: true,
   143  		},
   144  		{
   145  			name: "install another instance of infra1/current contract on a cluster already initialized with core/current contract + infra1/current contract, same namespace of the existing infra1",
   146  			fields: fields{
   147  				proxy: test.NewFakeProxy().
   148  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system").
   149  					WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "n1"),
   150  				installQueue: []repository.Components{
   151  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "n1"),
   152  				},
   153  			},
   154  			wantErr: true,
   155  		},
   156  		{
   157  			name: "install another instance of infra1/current contract on a cluster already initialized with core/current contract + infra1/current contract, different namespace of the existing infra1",
   158  			fields: fields{
   159  				proxy: test.NewFakeProxy().
   160  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system").
   161  					WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "n1"),
   162  				installQueue: []repository.Components{
   163  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "n2"),
   164  				},
   165  			},
   166  			wantErr: true,
   167  		},
   168  		{
   169  			name: "install core/previous contract + infra1/previous contract on an empty cluster (not supported)",
   170  			fields: fields{
   171  				proxy: test.NewFakeProxy(), // empty cluster
   172  				installQueue: []repository.Components{
   173  					newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v0.9.0", "cluster-api-system"),
   174  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v0.9.0", "infra1-system"),
   175  				},
   176  			},
   177  			wantErr: true,
   178  		},
   179  		{
   180  			name: "install core/previous contract + infra1/current contract on an empty cluster (not supported)",
   181  			fields: fields{
   182  				proxy: test.NewFakeProxy(), // empty cluster
   183  				installQueue: []repository.Components{
   184  					newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v0.9.0", "cluster-api-system"),
   185  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v1.0.0", "infra1-system"),
   186  				},
   187  			},
   188  			wantErr: true,
   189  		},
   190  		{
   191  			name: "install infra1/previous contract (not supported) on a cluster already initialized with core/current contract",
   192  			fields: fields{
   193  				proxy: test.NewFakeProxy().
   194  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "ns1"),
   195  				installQueue: []repository.Components{
   196  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v0.9.0", "infra1-system"),
   197  				},
   198  			},
   199  			wantErr: true,
   200  		},
   201  		{
   202  			name: "install core/next contract + infra1/next contract on an empty cluster (not supported)",
   203  			fields: fields{
   204  				proxy: test.NewFakeProxy(), // empty cluster
   205  				installQueue: []repository.Components{
   206  					newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v2.0.0", "cluster-api-system"),
   207  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra1-system"),
   208  				},
   209  			},
   210  			wantErr: true,
   211  		},
   212  		{
   213  			name: "install core/current contract + infra1/next contract on an empty cluster (not supported)",
   214  			fields: fields{
   215  				proxy: test.NewFakeProxy(), // empty cluster
   216  				installQueue: []repository.Components{
   217  					newFakeComponents("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
   218  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra1-system"),
   219  				},
   220  			},
   221  			wantErr: true,
   222  		},
   223  		{
   224  			name: "install infra1/next contract (not supported) on a cluster already initialized with core/current contract",
   225  			fields: fields{
   226  				proxy: test.NewFakeProxy().
   227  					WithProviderInventory("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "ns1"),
   228  				installQueue: []repository.Components{
   229  					newFakeComponents("infra1", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra1-system"),
   230  				},
   231  			},
   232  			wantErr: true,
   233  		},
   234  	}
   235  
   236  	for _, tt := range tests {
   237  		t.Run(tt.name, func(t *testing.T) {
   238  			g := NewWithT(t)
   239  
   240  			ctx := context.Background()
   241  
   242  			configClient, _ := config.New(ctx, "", config.InjectReader(fakeReader))
   243  
   244  			i := &providerInstaller{
   245  				configClient:      configClient,
   246  				proxy:             tt.fields.proxy,
   247  				providerInventory: newInventoryClient(tt.fields.proxy, nil),
   248  				repositoryClientFactory: func(ctx context.Context, provider config.Provider, configClient config.Client, _ ...repository.Option) (repository.Client, error) {
   249  					return repository.New(ctx, provider, configClient, repository.InjectRepository(repositoryMap[provider.ManifestLabel()]))
   250  				},
   251  				installQueue: tt.fields.installQueue,
   252  			}
   253  
   254  			err := i.Validate(ctx)
   255  			if tt.wantErr {
   256  				g.Expect(err).To(HaveOccurred())
   257  			} else {
   258  				g.Expect(err).ToNot(HaveOccurred())
   259  			}
   260  		})
   261  	}
   262  }
   263  
   264  func Test_providerInstaller_ValidateCRDName(t *testing.T) {
   265  	tests := []struct {
   266  		name    string
   267  		crd     unstructured.Unstructured
   268  		gk      *schema.GroupKind
   269  		wantErr bool
   270  	}{
   271  		{
   272  			name:    "CRD with valid name",
   273  			crd:     newFakeCRD("clusterclasses.cluster.x-k8s.io", nil),
   274  			gk:      &schema.GroupKind{Group: "cluster.x-k8s.io", Kind: "ClusterClass"},
   275  			wantErr: false,
   276  		},
   277  		{
   278  			name:    "CRD with invalid name",
   279  			crd:     newFakeCRD("clusterclass.cluster.x-k8s.io", nil),
   280  			gk:      &schema.GroupKind{Group: "cluster.x-k8s.io", Kind: "ClusterClass"},
   281  			wantErr: true,
   282  		},
   283  		{
   284  			name: "CRD with invalid name but has skip annotation",
   285  			crd: newFakeCRD("clusterclass.cluster.x-k8s.io", map[string]string{
   286  				clusterctlv1.SkipCRDNamePreflightCheckAnnotation: "",
   287  			}),
   288  			gk:      &schema.GroupKind{Group: "cluster.x-k8s.io", Kind: "ClusterClass"},
   289  			wantErr: false,
   290  		},
   291  	}
   292  
   293  	for _, tt := range tests {
   294  		t.Run(tt.name, func(t *testing.T) {
   295  			g := NewWithT(t)
   296  
   297  			err := validateCRDName(tt.crd, tt.gk)
   298  			if tt.wantErr {
   299  				g.Expect(err).To(HaveOccurred())
   300  			} else {
   301  				g.Expect(err).ToNot(HaveOccurred())
   302  			}
   303  		})
   304  	}
   305  }
   306  
   307  type fakeComponents struct {
   308  	config.Provider
   309  	inventoryObject clusterctlv1.Provider
   310  }
   311  
   312  func (c *fakeComponents) Version() string {
   313  	panic("not implemented")
   314  }
   315  
   316  func (c *fakeComponents) Variables() []string {
   317  	panic("not implemented")
   318  }
   319  
   320  func (c *fakeComponents) Images() []string {
   321  	panic("not implemented")
   322  }
   323  
   324  func (c *fakeComponents) TargetNamespace() string {
   325  	panic("not implemented")
   326  }
   327  
   328  func (c *fakeComponents) InventoryObject() clusterctlv1.Provider {
   329  	return c.inventoryObject
   330  }
   331  
   332  func (c *fakeComponents) Objs() []unstructured.Unstructured {
   333  	return []unstructured.Unstructured{}
   334  }
   335  
   336  func (c *fakeComponents) Yaml() ([]byte, error) {
   337  	panic("not implemented")
   338  }
   339  
   340  func newFakeComponents(name string, providerType clusterctlv1.ProviderType, version, targetNamespace string) repository.Components {
   341  	inventoryObject := fakeProvider(name, providerType, version, targetNamespace)
   342  	return &fakeComponents{
   343  		Provider:        config.NewProvider(inventoryObject.ProviderName, "", clusterctlv1.ProviderType(inventoryObject.Type)),
   344  		inventoryObject: inventoryObject,
   345  	}
   346  }
   347  
   348  func newFakeCRD(name string, annotations map[string]string) unstructured.Unstructured {
   349  	u := unstructured.Unstructured{}
   350  	u.SetName(name)
   351  	u.SetAnnotations(annotations)
   352  	return u
   353  }