sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/upgrade_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 client
    18  
    19  import (
    20  	"context"
    21  	"sort"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	. "github.com/onsi/gomega"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    30  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
    31  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
    32  	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
    33  )
    34  
    35  func Test_clusterctlClient_PlanCertUpgrade(t *testing.T) {
    36  	// create a fake config with a provider named P1 and a variable named var
    37  	repository1Config := config.NewProvider("p1", "url", clusterctlv1.CoreProviderType)
    38  
    39  	ctx := context.Background()
    40  
    41  	config1 := newFakeConfig(ctx).
    42  		WithVar("var", "value").
    43  		WithProvider(repository1Config)
    44  
    45  	// create a fake repository with some YAML files in it (usually matching
    46  	// the list of providers defined in the config)
    47  	repository1 := newFakeRepository(ctx, repository1Config, config1).
    48  		WithPaths("root", "components").
    49  		WithDefaultVersion("v1.0").
    50  		WithFile("v1.0", "components.yaml", []byte("content"))
    51  
    52  	certManagerPlan := CertManagerUpgradePlan{
    53  		From:          "v0.16.1",
    54  		To:            "v1.1.0",
    55  		ShouldUpgrade: true,
    56  	}
    57  	// create a fake cluster, with a cert manager client that has an upgrade
    58  	// plan
    59  	cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "cluster1"}, config1).
    60  		WithCertManagerClient(newFakeCertManagerClient(nil, nil).WithCertManagerPlan(certManagerPlan))
    61  
    62  	client := newFakeClient(ctx, config1).
    63  		WithRepository(repository1).
    64  		WithCluster(cluster1)
    65  
    66  	tests := []struct {
    67  		name      string
    68  		client    *fakeClient
    69  		expectErr bool
    70  	}{
    71  		{
    72  			name:      "returns plan for upgrading cert-manager",
    73  			client:    client,
    74  			expectErr: false,
    75  		},
    76  	}
    77  
    78  	for _, tt := range tests {
    79  		t.Run(tt.name, func(t *testing.T) {
    80  			g := NewWithT(t)
    81  
    82  			ctx := context.Background()
    83  
    84  			options := PlanUpgradeOptions{
    85  				Kubeconfig: Kubeconfig{Path: "cluster1"},
    86  			}
    87  			actualPlan, err := tt.client.PlanCertManagerUpgrade(ctx, options)
    88  			if tt.expectErr {
    89  				g.Expect(err).To(HaveOccurred())
    90  				g.Expect(actualPlan).To(BeComparableTo(CertManagerUpgradePlan{}))
    91  				return
    92  			}
    93  			g.Expect(err).ToNot(HaveOccurred())
    94  			g.Expect(actualPlan).To(BeComparableTo(certManagerPlan))
    95  		})
    96  	}
    97  }
    98  
    99  func Test_clusterctlClient_PlanUpgrade(t *testing.T) {
   100  	type fields struct {
   101  		client *fakeClient
   102  	}
   103  	type args struct {
   104  		options PlanUpgradeOptions
   105  	}
   106  	tests := []struct {
   107  		name    string
   108  		fields  fields
   109  		args    args
   110  		wantErr bool
   111  	}{
   112  		{
   113  			name: "does not return error if cluster client is found",
   114  			fields: fields{
   115  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   116  			},
   117  			args: args{
   118  				options: PlanUpgradeOptions{
   119  					Kubeconfig: Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
   120  				},
   121  			},
   122  			wantErr: false,
   123  		},
   124  		{
   125  			name: "returns an error if cluster client is not found",
   126  			fields: fields{
   127  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   128  			},
   129  			args: args{
   130  				options: PlanUpgradeOptions{
   131  					Kubeconfig: Kubeconfig{Path: "kubeconfig", Context: "some-other-context"},
   132  				},
   133  			},
   134  			wantErr: true,
   135  		},
   136  	}
   137  
   138  	for _, tt := range tests {
   139  		t.Run(tt.name, func(t *testing.T) {
   140  			g := NewWithT(t)
   141  
   142  			ctx := context.Background()
   143  
   144  			_, err := tt.fields.client.PlanUpgrade(ctx, tt.args.options)
   145  			if tt.wantErr {
   146  				g.Expect(err).To(HaveOccurred())
   147  				return
   148  			}
   149  			g.Expect(err).ToNot(HaveOccurred())
   150  		})
   151  	}
   152  }
   153  
   154  func Test_clusterctlClient_ApplyUpgrade(t *testing.T) {
   155  	type fields struct {
   156  		client *fakeClient
   157  	}
   158  	type args struct {
   159  		options ApplyUpgradeOptions
   160  	}
   161  	tests := []struct {
   162  		name          string
   163  		fields        fields
   164  		args          args
   165  		wantProviders *clusterctlv1.ProviderList
   166  		wantErr       bool
   167  	}{
   168  		{
   169  			name: "apply a plan",
   170  			fields: fields{
   171  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   172  			},
   173  			args: args{
   174  				options: ApplyUpgradeOptions{
   175  					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
   176  					Contract:                test.CurrentCAPIContract,
   177  					CoreProvider:            "",
   178  					BootstrapProviders:      nil,
   179  					ControlPlaneProviders:   nil,
   180  					InfrastructureProviders: nil,
   181  				},
   182  			},
   183  			wantProviders: &clusterctlv1.ProviderList{
   184  				ListMeta: metav1.ListMeta{},
   185  				Items: []clusterctlv1.Provider{ // both providers should be upgraded
   186  					fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"),
   187  					fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"),
   188  				},
   189  			},
   190  			wantErr: false,
   191  		},
   192  		{
   193  			name: "apply a custom plan - core provider only",
   194  			fields: fields{
   195  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   196  			},
   197  			args: args{
   198  				options: ApplyUpgradeOptions{
   199  					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
   200  					Contract:                "",
   201  					CoreProvider:            "cluster-api-system/cluster-api:v1.0.1",
   202  					BootstrapProviders:      nil,
   203  					ControlPlaneProviders:   nil,
   204  					InfrastructureProviders: nil,
   205  				},
   206  			},
   207  			wantProviders: &clusterctlv1.ProviderList{
   208  				ListMeta: metav1.ListMeta{},
   209  				Items: []clusterctlv1.Provider{ // only one provider should be upgraded
   210  					fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"),
   211  					fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.0", "infra-system"),
   212  				},
   213  			},
   214  			wantErr: false,
   215  		},
   216  		{
   217  			name: "apply a custom plan - infra provider only",
   218  			fields: fields{
   219  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   220  			},
   221  			args: args{
   222  				options: ApplyUpgradeOptions{
   223  					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
   224  					Contract:                "",
   225  					CoreProvider:            "",
   226  					BootstrapProviders:      nil,
   227  					ControlPlaneProviders:   nil,
   228  					InfrastructureProviders: []string{"infra-system/infra:v2.0.1"},
   229  				},
   230  			},
   231  			wantProviders: &clusterctlv1.ProviderList{
   232  				ListMeta: metav1.ListMeta{},
   233  				Items: []clusterctlv1.Provider{ // only one provider should be upgraded
   234  					fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.0", "cluster-api-system"),
   235  					fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"),
   236  				},
   237  			},
   238  			wantErr: false,
   239  		},
   240  		{
   241  			name: "apply a custom plan - both providers",
   242  			fields: fields{
   243  				client: fakeClientForUpgrade(), // core v1.0.0 (v1.0.1 available), infra v2.0.0 (v2.0.1 available)
   244  			},
   245  			args: args{
   246  				options: ApplyUpgradeOptions{
   247  					Kubeconfig:              Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"},
   248  					Contract:                "",
   249  					CoreProvider:            "cluster-api-system/cluster-api:v1.0.1",
   250  					BootstrapProviders:      nil,
   251  					ControlPlaneProviders:   nil,
   252  					InfrastructureProviders: []string{"infra-system/infra:v2.0.1"},
   253  				},
   254  			},
   255  			wantProviders: &clusterctlv1.ProviderList{
   256  				ListMeta: metav1.ListMeta{},
   257  				Items: []clusterctlv1.Provider{
   258  					fakeProvider("cluster-api", clusterctlv1.CoreProviderType, "v1.0.1", "cluster-api-system"),
   259  					fakeProvider("infra", clusterctlv1.InfrastructureProviderType, "v2.0.1", "infra-system"),
   260  				},
   261  			},
   262  			wantErr: false,
   263  		},
   264  	}
   265  	for _, tt := range tests {
   266  		t.Run(tt.name, func(t *testing.T) {
   267  			g := NewWithT(t)
   268  
   269  			ctx := context.Background()
   270  
   271  			err := tt.fields.client.ApplyUpgrade(ctx, tt.args.options)
   272  			if tt.wantErr {
   273  				g.Expect(err).To(HaveOccurred())
   274  				return
   275  			}
   276  			g.Expect(err).ToNot(HaveOccurred())
   277  
   278  			// converting between client and cluster alias for Kubeconfig
   279  			input := cluster.Kubeconfig(tt.args.options.Kubeconfig)
   280  			proxy := tt.fields.client.clusters[input].Proxy()
   281  			gotProviders := &clusterctlv1.ProviderList{}
   282  
   283  			c, err := proxy.NewClient(ctx)
   284  			g.Expect(err).ToNot(HaveOccurred())
   285  
   286  			g.Expect(c.List(ctx, gotProviders)).To(Succeed())
   287  
   288  			sort.Slice(gotProviders.Items, func(i, j int) bool {
   289  				return gotProviders.Items[i].Name < gotProviders.Items[j].Name
   290  			})
   291  			sort.Slice(tt.wantProviders.Items, func(i, j int) bool {
   292  				return tt.wantProviders.Items[i].Name < tt.wantProviders.Items[j].Name
   293  			})
   294  			for i := range gotProviders.Items {
   295  				tt.wantProviders.Items[i].ResourceVersion = gotProviders.Items[i].ResourceVersion
   296  			}
   297  			g.Expect(gotProviders).To(BeComparableTo(tt.wantProviders), cmp.Diff(gotProviders, tt.wantProviders))
   298  		})
   299  	}
   300  }
   301  
   302  func fakeClientForUpgrade() *fakeClient {
   303  	core := config.NewProvider("cluster-api", "https://somewhere.com", clusterctlv1.CoreProviderType)
   304  	infra := config.NewProvider("infra", "https://somewhere.com", clusterctlv1.InfrastructureProviderType)
   305  
   306  	ctx := context.Background()
   307  
   308  	config1 := newFakeConfig(ctx).
   309  		WithProvider(core).
   310  		WithProvider(infra)
   311  
   312  	repository1 := newFakeRepository(ctx, core, config1).
   313  		WithPaths("root", "components.yaml").
   314  		WithDefaultVersion("v1.0.1").
   315  		WithFile("v1.0.1", "components.yaml", componentsYAML("ns2")).
   316  		WithVersions("v1.0.0", "v1.0.1").
   317  		WithMetadata("v1.0.1", &clusterctlv1.Metadata{
   318  			ReleaseSeries: []clusterctlv1.ReleaseSeries{
   319  				{Major: 1, Minor: 0, Contract: test.CurrentCAPIContract},
   320  			},
   321  		})
   322  	repository2 := newFakeRepository(ctx, infra, config1).
   323  		WithPaths("root", "components.yaml").
   324  		WithDefaultVersion("v2.0.0").
   325  		WithFile("v2.0.1", "components.yaml", componentsYAML("ns2")).
   326  		WithVersions("v2.0.0", "v2.0.1").
   327  		WithMetadata("v2.0.1", &clusterctlv1.Metadata{
   328  			ReleaseSeries: []clusterctlv1.ReleaseSeries{
   329  				{Major: 2, Minor: 0, Contract: test.CurrentCAPIContract},
   330  			},
   331  		})
   332  
   333  	cluster1 := newFakeCluster(cluster.Kubeconfig{Path: "kubeconfig", Context: "mgmt-context"}, config1).
   334  		WithRepository(repository1).
   335  		WithRepository(repository2).
   336  		WithProviderInventory(core.Name(), core.Type(), "v1.0.0", "cluster-api-system").
   337  		WithProviderInventory(infra.Name(), infra.Type(), "v2.0.0", "infra-system").
   338  		WithObjs(test.FakeCAPISetupObjects()...)
   339  
   340  	client := newFakeClient(ctx, config1).
   341  		WithRepository(repository1).
   342  		WithRepository(repository2).
   343  		WithCluster(cluster1)
   344  
   345  	return client
   346  }
   347  
   348  func fakeProvider(name string, providerType clusterctlv1.ProviderType, version, targetNamespace string) clusterctlv1.Provider {
   349  	return clusterctlv1.Provider{
   350  		TypeMeta: metav1.TypeMeta{
   351  			APIVersion: clusterctlv1.GroupVersion.String(),
   352  			Kind:       "Provider",
   353  		},
   354  		ObjectMeta: metav1.ObjectMeta{
   355  			Namespace: targetNamespace,
   356  			Name:      clusterctlv1.ManifestLabel(name, providerType),
   357  			Labels: map[string]string{
   358  				clusterctlv1.ClusterctlLabel:     "",
   359  				clusterv1.ProviderNameLabel:      clusterctlv1.ManifestLabel(name, providerType),
   360  				clusterctlv1.ClusterctlCoreLabel: clusterctlv1.ClusterctlCoreLabelInventoryValue,
   361  			},
   362  		},
   363  		ProviderName:     name,
   364  		Type:             string(providerType),
   365  		Version:          version,
   366  		WatchedNamespace: "",
   367  	}
   368  }
   369  
   370  func Test_parseUpgradeItem(t *testing.T) {
   371  	type args struct {
   372  		provider string
   373  	}
   374  
   375  	ctx := context.Background()
   376  
   377  	configClient := newFakeConfig(ctx)
   378  	clusterClient := newFakeCluster(cluster.Kubeconfig{Path: "cluster1"}, configClient)
   379  	clusterClient.WithProviderInventory("best-provider", clusterctlv1.CoreProviderType, "v1.0.0", "best-provider-system")
   380  
   381  	tests := []struct {
   382  		name    string
   383  		args    args
   384  		want    *cluster.UpgradeItem
   385  		wantErr bool
   386  	}{
   387  		{
   388  			name: "namespace/provider",
   389  			args: args{
   390  				provider: "namespace/provider",
   391  			},
   392  			want: &cluster.UpgradeItem{
   393  				Provider: clusterctlv1.Provider{
   394  					ObjectMeta: metav1.ObjectMeta{
   395  						Namespace: "namespace",
   396  						Name:      clusterctlv1.ManifestLabel("provider", clusterctlv1.CoreProviderType),
   397  					},
   398  					ProviderName: "provider",
   399  					Type:         string(clusterctlv1.CoreProviderType),
   400  				},
   401  				NextVersion: "",
   402  			},
   403  			wantErr: false,
   404  		},
   405  		{
   406  			name: "namespace/provider:version",
   407  			args: args{
   408  				provider: "namespace/provider:version",
   409  			},
   410  			want: &cluster.UpgradeItem{
   411  				Provider: clusterctlv1.Provider{
   412  					ObjectMeta: metav1.ObjectMeta{
   413  						Namespace: "namespace",
   414  						Name:      clusterctlv1.ManifestLabel("provider", clusterctlv1.CoreProviderType),
   415  					},
   416  					ProviderName: "provider",
   417  					Type:         string(clusterctlv1.CoreProviderType),
   418  				},
   419  				NextVersion: "version",
   420  			},
   421  			wantErr: false,
   422  		},
   423  		{
   424  			name: "provider:version",
   425  			args: args{
   426  				provider: "best-provider:v1.0.0",
   427  			},
   428  			want: &cluster.UpgradeItem{
   429  				Provider: clusterctlv1.Provider{
   430  					ObjectMeta: metav1.ObjectMeta{
   431  						Namespace: "best-provider-system",
   432  						Name:      clusterctlv1.ManifestLabel("best-provider", clusterctlv1.CoreProviderType),
   433  					},
   434  					ProviderName: "best-provider",
   435  					Type:         string(clusterctlv1.CoreProviderType),
   436  				},
   437  				NextVersion: "v1.0.0",
   438  			},
   439  			wantErr: false,
   440  		},
   441  		{
   442  			name: "provider: with no version",
   443  			args: args{
   444  				provider: "provider:",
   445  			},
   446  			want:    nil,
   447  			wantErr: true,
   448  		},
   449  		{
   450  			name: "provider with no version",
   451  			args: args{
   452  				provider: "provider",
   453  			},
   454  			want:    nil,
   455  			wantErr: true,
   456  		},
   457  		{
   458  			name: "namespace empty",
   459  			args: args{
   460  				provider: "/provider:version",
   461  			},
   462  			want:    nil,
   463  			wantErr: true,
   464  		},
   465  	}
   466  	for _, tt := range tests {
   467  		t.Run(tt.name, func(t *testing.T) {
   468  			g := NewWithT(t)
   469  
   470  			ctx := context.Background()
   471  
   472  			got, err := parseUpgradeItem(ctx, clusterClient, tt.args.provider, clusterctlv1.CoreProviderType)
   473  			if tt.wantErr {
   474  				g.Expect(err).To(HaveOccurred())
   475  				return
   476  			}
   477  			g.Expect(err).ToNot(HaveOccurred())
   478  
   479  			g.Expect(got).To(BeComparableTo(tt.want))
   480  		})
   481  	}
   482  }