From 12d2b5170fa667d425a1438da96381a5fbbe78e6 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Sun, 13 Nov 2022 20:48:04 -0800 Subject: [PATCH 01/10] incremental --- script/e2e-test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/e2e-test.sh b/script/e2e-test.sh index de25aec8ea6..b6a6a47b564 100755 --- a/script/e2e-test.sh +++ b/script/e2e-test.sh @@ -250,9 +250,9 @@ installFlux() { kubectl apply -f "${url}" # wait for deployment to be ready - kubectl rollout status -w deployment/helm-controller --namespace="${namespace}" - kubectl rollout status -w deployment/source-controller --namespace="${namespace}" - + k8s_wait_for_deployment "${namespace}" helm-controller + k8s_wait_for_deployment "${namespace}" source-controller + # Add test repository. kubectl apply -f https://raw.githubusercontent.com/fluxcd/source-controller/main/config/samples/source_v1beta2_helmrepository.yaml From 7b0a25142c0562d55c92a6c077c03580f5ebe819 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Tue, 15 Nov 2022 19:14:26 -0800 Subject: [PATCH 02/10] incremental --- .../packages/v1alpha1/cache/chart_cache.go | 51 ++++++- .../plugins/fluxv2/packages/v1alpha1/chart.go | 2 +- .../v1alpha1/chart_integration_test.go | 127 ++++++++++++++++++ .../fluxv2/packages/v1alpha1/chart_test.go | 24 +--- .../packages/v1alpha1/global_vars_test.go | 55 +++++++- .../fluxv2/packages/v1alpha1/oci_repo.go | 8 +- .../fluxv2/packages/v1alpha1/release_test.go | 26 ---- .../plugins/fluxv2/packages/v1alpha1/repo.go | 4 +- .../v1alpha1/repo_integration_test.go | 41 +----- .../fluxv2/packages/v1alpha1/repo_test.go | 39 +----- .../fluxv2/packages/v1alpha1/server_test.go | 23 +++- .../packages/v1alpha1/test_util_test.go | 126 ++++++++++++++++- 12 files changed, 385 insertions(+), 141 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go index 41283de8fad..eff6c175132 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go @@ -259,10 +259,9 @@ func (c *ChartCache) processNextWorkItem(workerName string) bool { return true } -func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { - log.Infof("+DeleteChartsForRepo(%s)", repo) - defer log.Infof("-DeleteChartsForRepo(%s)", repo) - +// will clear out the cache of charts for a given repo except the charts specified by +// keepThese argument, which may be nil. +func (c *ChartCache) deleteChartsHelper(repo *types.NamespacedName, keepThese sets.String) error { // need to get a list of all charts/versions for this repo that are either: // a. already in the cache OR // b. being processed @@ -287,6 +286,7 @@ func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { if err != nil { return err } + log.Infof("Redis [SCAN %d %s]: %d keys", cursor, match, len(keys)) for _, k := range keys { redisKeysToDelete.Insert(k) } @@ -308,7 +308,7 @@ func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { } } - for k := range redisKeysToDelete { + for k := range redisKeysToDelete.Difference(keepThese) { if namespace, chartID, chartVersion, err := c.fromKey(k); err != nil { log.Errorf("%+v", err) } else { @@ -329,6 +329,47 @@ func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { return nil } +func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { + log.Infof("+DeleteChartsForRepo(%s)", repo) + defer log.Infof("-DeleteChartsForRepo(%s)", repo) + + return c.deleteChartsHelper(repo, sets.String{}) +} + +func (c *ChartCache) PurgeObsoleteChartVersions(keepThese []models.Chart) error { + log.Infof("+PurgeObsoleteChartVersions()") + defer log.Infof("-PurgeObsoleteChartVersions") + + repos := map[types.NamespacedName]sets.String{} + for _, ch := range keepThese { + if ch.Repo == nil { + continue + } + n := types.NamespacedName{ + Name: ch.Repo.Name, + Namespace: ch.Repo.Namespace, + } + a, ok := repos[n] + if a == nil || !ok { + a = sets.String{} + } + for _, cv := range ch.ChartVersions { + if key, err := c.KeyFor(ch.Repo.Namespace, ch.ID, cv.Version); err != nil { + return err + } else { + repos[n] = a.Insert(key) + } + } + } + + for repo, keep := range repos { + if err := c.deleteChartsHelper(&repo, keep); err != nil { + return err + } + } + return nil +} + func (c *ChartCache) OnResync() error { log.Infof("+OnResync(), queue: [%s], size: [%d]", c.queue.Name(), c.queue.Len()) c.resyncCond.L.Lock() diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart.go index fc8d505cccc..d618b627e69 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart.go @@ -108,7 +108,7 @@ func (s *Server) availableChartDetail(ctx context.Context, packageRef *corev1.Av } if byteArray == nil { - return nil, status.Errorf(codes.Internal, "failed to load details for chart [%s]", chartModel.ID) + return nil, status.Errorf(codes.Internal, "failed to load details for chart [%s], version [%s]", chartModel.ID, chartVersion) } } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go index 721758ed859..f0f02e83e41 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go @@ -1040,3 +1040,130 @@ func TestKindClusterAvailablePackageEndpointsOCIRepo2Charts(t *testing.T) { }) } } + +// The goal of this integration test is to ensure that when the contents of remote HTTP helm repo is changed, +// that fact is recorded locally and processed properly (repo/chart cache is updated with latest, etc.) +func TestKindClusterAddRemovePackageVersionsInHttpRepo(t *testing.T) { + fluxPluginPackagesClient, _, err := checkEnv(t) + if err != nil { + t.Fatal(err) + } + + adminAcctName := types.NamespacedName{ + Name: "test-add-remove-versions-repo-admin-" + randSeq(4), + Namespace: "default", + } + grpcContext, err := newGrpcAdminContext(t, adminAcctName) + if err != nil { + t.Fatal(err) + } + + repoName := types.NamespacedName{ + Name: "podinfo", + Namespace: "test-" + randSeq(4), + } + if err := kubeCreateNamespaceAndCleanup(t, repoName.Namespace); err != nil { + t.Fatal(err) + } + + if err = kubeAddHelmRepositoryAndCleanup(t, repoName, "", podinfo_repo_url, "", 10*time.Second); err != nil { + t.Fatal(err) + } + + pkgRef := availableRef(fmt.Sprintf("%s/%s", repoName.Name, "podinfo"), repoName.Namespace) + + // need to wait until repo is indexed by flux plugin + const maxWait = 25 + var pkgDetail *corev1.GetAvailablePackageDetailResponse + for i := 0; i <= maxWait; i++ { + grpcContext, cancel := context.WithTimeout(grpcContext, defaultContextTimeout) + defer cancel() + + pkgDetail, err = fluxPluginPackagesClient.GetAvailablePackageDetail( + grpcContext, + &corev1.GetAvailablePackageDetailRequest{AvailablePackageRef: pkgRef}) + if err == nil { + break + } else if i == maxWait { + if repo, err2 := kubeGetHelmRepository(t, repoName); err2 == nil && repo != nil { + t.Fatalf("Timed out waiting for available package [%s], last response: %v, last error: [%v],\nhelm repository:%s", + pkgRef, pkgDetail, err, common.PrettyPrint(repo)) + } else { + t.Fatalf("Timed out waiting for available package [%s], last response: %v, last error: [%v]", + pkgRef, pkgDetail, err) + } + } else { + t.Logf("Waiting 1s for repository [%s] to be indexed, attempt [%d/%d]...", repoName, i+1, maxWait) + time.Sleep(1 * time.Second) + } + } + compareActualVsExpectedAvailablePackageDetail( + t, + pkgDetail.AvailablePackageDetail, + expected_detail_podinfo(repoName.Name, repoName.Namespace).AvailablePackageDetail) + + podName, err := getFluxPluginTestdataPodName() + if err != nil { + t.Fatal(err) + } + t.Logf("podName = [%s]", podName) + + if err = kubeCopyFileToPod( + t, + testTgz("podinfo-6.0.3.tgz"), + *podName, + "/usr/share/nginx/html/podinfo/podinfo-6.0.3.tgz"); err != nil { + t.Fatal(err) + } + if err = kubeCopyFileToPod( + t, + testYaml("podinfo-index-updated.yaml"), + *podName, + "/usr/share/nginx/html/podinfo/index.yaml"); err != nil { + t.Fatal(err) + } + + SleepWithCountdown(t, 20) + + pkgDetail, err = fluxPluginPackagesClient.GetAvailablePackageDetail( + grpcContext, + &corev1.GetAvailablePackageDetailRequest{AvailablePackageRef: pkgRef}) + if err != nil { + t.Fatal(err) + } + compareActualVsExpectedAvailablePackageDetail( + t, + pkgDetail.AvailablePackageDetail, + expected_detail_podinfo_after_update_1(repoName.Name, repoName.Namespace).AvailablePackageDetail) + + if err = kubeCopyFileToPod( + t, + testYaml("podinfo-index.yaml"), + *podName, + "/usr/share/nginx/html/podinfo/index.yaml"); err != nil { + t.Logf("Error reverting to previous podinfo index: %v", err) + } + + SleepWithCountdown(t, 20) + + pkgDetail, err = fluxPluginPackagesClient.GetAvailablePackageDetail( + grpcContext, + &corev1.GetAvailablePackageDetailRequest{AvailablePackageRef: pkgRef}) + if err != nil { + t.Fatal(err) + } + compareActualVsExpectedAvailablePackageDetail( + t, + pkgDetail.AvailablePackageDetail, + expected_detail_podinfo(repoName.Name, repoName.Namespace).AvailablePackageDetail) + + _, err = fluxPluginPackagesClient.GetAvailablePackageDetail( + grpcContext, + &corev1.GetAvailablePackageDetailRequest{ + AvailablePackageRef: pkgRef, + PkgVersion: "6.0.3", + }) + if status.Code(err) != codes.Internal { + t.Fatalf("Expected Internal, got: %v", err) + } +} diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go index c2ff36ad0a6..c45b098bbf5 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go @@ -19,7 +19,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" corev1 "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/packages/v1alpha1" - plugins "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/plugins/v1alpha1" "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache" "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/common" httpclient "github.com/vmware-tanzu/kubeapps/pkg/http-client" @@ -860,6 +859,9 @@ func TestChartCacheResyncNotIdle(t *testing.T) { redisMockSetValueForRepo(mock, repoKey, repoBytes, nil) } + match := fmt.Sprintf("helmcharts:%s:%s/*:*", repoNamespace, repoName) + mock.ExpectScan(0, match, 0).SetVal([]string{}, 0) + opts := &common.HttpClientOptions{} chartCacheKeys := []string{} var chartBytes []byte @@ -922,6 +924,7 @@ func TestChartCacheResyncNotIdle(t *testing.T) { } else { mock.ExpectFlushDB().SetVal("OK") redisMockSetValueForRepo(mock, repoKey, repoBytes, nil) + mock.ExpectScan(0, match, 0).SetVal([]string{}, 0) // now we can signal to the server it's ok to proceed repoResyncCh <- 0 @@ -1274,22 +1277,3 @@ func fromRedisKeyForChart(key string) (namespace, chartID, chartVersion string, } return parts[1], parts[2], parts[3], nil } - -func compareActualVsExpectedAvailablePackageDetail(t *testing.T, actual *corev1.AvailablePackageDetail, expected *corev1.AvailablePackageDetail) { - opt1 := cmpopts.IgnoreUnexported(corev1.AvailablePackageDetail{}, corev1.AvailablePackageReference{}, corev1.Context{}, corev1.Maintainer{}, plugins.Plugin{}, corev1.PackageAppVersion{}) - // these few fields a bit special in that they are all very long strings, - // so we'll do a 'Contains' check for these instead of 'Equals' - opt2 := cmpopts.IgnoreFields(corev1.AvailablePackageDetail{}, "Readme", "DefaultValues", "ValuesSchema") - if got, want := actual, expected; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } - if !strings.Contains(actual.Readme, expected.Readme) { - t.Errorf("substring mismatch (-want: %s\n+got: %s):\n", expected.Readme, actual.Readme) - } - if !strings.Contains(actual.DefaultValues, expected.DefaultValues) { - t.Errorf("substring mismatch (-want: %s\n+got: %s):\n", expected.DefaultValues, actual.DefaultValues) - } - if !strings.Contains(actual.ValuesSchema, expected.ValuesSchema) { - t.Errorf("substring mismatch (-want: %s\n+got: %s):\n", expected.ValuesSchema, actual.ValuesSchema) - } -} diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go index 6de66aacf43..15efe732373 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go @@ -766,6 +766,46 @@ var ( } } + expected_detail_podinfo = func(name, namespace string) *corev1.GetAvailablePackageDetailResponse { + return &corev1.GetAvailablePackageDetailResponse{ + AvailablePackageDetail: &corev1.AvailablePackageDetail{ + AvailablePackageRef: availableRef(name+"/podinfo", namespace), + Name: "podinfo", + Version: pkgAppVersion("6.0.0"), + RepoUrl: "http://fluxv2plugin-testdata-svc.default.svc.cluster.local:80/podinfo", + HomeUrl: "https://github.com/stefanprodan/podinfo", + DisplayName: "podinfo", + ShortDescription: "Podinfo Helm chart for Kubernetes", + SourceUrls: []string{"https://github.com/stefanprodan/podinfo"}, + Maintainers: []*corev1.Maintainer{ + {Name: "stefanprodan", Email: "stefanprodan@users.noreply.github.com"}, + }, + Readme: "Podinfo is used by CNCF projects like [Flux](https://github.com/fluxcd/flux2)", + DefaultValues: "Default values for podinfo.\n\nreplicaCount: 1\n", + }, + } + } + + expected_detail_podinfo_after_update_1 = func(name, namespace string) *corev1.GetAvailablePackageDetailResponse { + return &corev1.GetAvailablePackageDetailResponse{ + AvailablePackageDetail: &corev1.AvailablePackageDetail{ + AvailablePackageRef: availableRef(name+"/podinfo", namespace), + Name: "podinfo", + Version: pkgAppVersion("6.0.3"), + RepoUrl: "http://fluxv2plugin-testdata-svc.default.svc.cluster.local:80/podinfo", + HomeUrl: "https://github.com/stefanprodan/podinfo", + DisplayName: "podinfo", + ShortDescription: "Podinfo Helm chart for Kubernetes", + SourceUrls: []string{"https://github.com/stefanprodan/podinfo"}, + Maintainers: []*corev1.Maintainer{ + {Name: "stefanprodan", Email: "stefanprodan@users.noreply.github.com"}, + }, + Readme: "Podinfo is used by CNCF projects like [Flux](https://github.com/fluxcd/flux2)", + DefaultValues: "Default values for podinfo.\n\nreplicaCount: 1\n", + }, + } + } + expected_detail_podinfo_basic_auth = func(name string) *corev1.GetAvailablePackageDetailResponse { return &corev1.GetAvailablePackageDetailResponse{ AvailablePackageDetail: &corev1.AvailablePackageDetail{ @@ -3700,21 +3740,26 @@ var ( } podinfo_repo_status_1 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '9d3ac1eb708dfaebae14d7c88fd46afce8b1e0f7aace790d91758575dc8ce518'", + Ready: true, + Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, + // the actual revision # (SHA digest), like + // '552fc7ab40d40adcd7adebad6d5b8185a5924bc2e2badee8468e20e6962d3c3e' + // may change depending on environment, flux version, + // or even the order in which tests are run, we we'll ignore that for + // the purpose of the integration test + UserReason: "Succeeded: stored artifact for revision '", } podinfo_repo_status_2 = &corev1.PackageRepositoryStatus{ Ready: true, Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '651f952130ea96823711d08345b85e82be011dc6'", + UserReason: "Succeeded: stored artifact for revision '", } podinfo_repo_status_3 = &corev1.PackageRepositoryStatus{ Ready: true, Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '2867920fb8f56575f4bc95ed878ee2a0c8ae79cdd2bca210a72aa3ff04defa1b'", + UserReason: "Succeeded: stored artifact for revision '", } podinfo_repo_status_4 = &corev1.PackageRepositoryStatus{ diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/oci_repo.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/oci_repo.go index 3bb6fb30f3b..47ad52760d2 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/oci_repo.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/oci_repo.go @@ -441,7 +441,9 @@ func (s *repoEventSink) onAddOciRepo(repo sourcev1.HelmRepository) ([]byte, bool if s.chartCache != nil { fn := downloadOCIChartFn(ociChartRepo) - if err = s.chartCache.SyncCharts(charts, fn); err != nil { + if err = s.chartCache.PurgeObsoleteChartVersions(charts); err != nil { + return nil, false, err + } else if err = s.chartCache.SyncCharts(charts, fn); err != nil { return nil, false, err } } @@ -521,7 +523,9 @@ func (s *repoEventSink) onModifyOciRepo(key string, oldValue interface{}, repo s if s.chartCache != nil { fn := downloadOCIChartFn(ociChartRepo) - if err = s.chartCache.SyncCharts(charts, fn); err != nil { + if err = s.chartCache.PurgeObsoleteChartVersions(charts); err != nil { + return nil, false, err + } else if err = s.chartCache.SyncCharts(charts, fn); err != nil { return nil, false, err } } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go index 102f825e0eb..0b09b705483 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go @@ -8,7 +8,6 @@ import ( "net/http" "net/http/httptest" "os" - "strings" "testing" "time" @@ -1232,31 +1231,6 @@ func newChartsAndReleases(t *testing.T, existingK8sObjs []testSpecGetInstalledPa return charts, releases, cleanup } -func compareActualVsExpectedGetInstalledPackageDetailResponse(t *testing.T, actualResp *corev1.GetInstalledPackageDetailResponse, expectedResp *corev1.GetInstalledPackageDetailResponse) { - opts := cmpopts.IgnoreUnexported( - corev1.GetInstalledPackageDetailResponse{}, - corev1.InstalledPackageDetail{}, - corev1.InstalledPackageReference{}, - corev1.Context{}, - corev1.VersionReference{}, - corev1.InstalledPackageStatus{}, - corev1.PackageAppVersion{}, - plugins.Plugin{}, - corev1.ReconciliationOptions{}, - corev1.AvailablePackageReference{}) - // see comment in release_integration_test.go. Intermittently we get an inconsistent error message from flux - opts2 := cmpopts.IgnoreFields(corev1.InstalledPackageStatus{}, "UserReason") - // Values Applied are JSON string and need to be compared as such - opts3 := cmpopts.IgnoreFields(corev1.InstalledPackageDetail{}, "ValuesApplied") - if got, want := actualResp, expectedResp; !cmp.Equal(want, got, opts, opts2, opts3) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2, opts3)) - } - if !strings.Contains(actualResp.InstalledPackageDetail.Status.UserReason, expectedResp.InstalledPackageDetail.Status.UserReason) { - t.Errorf("substring mismatch (-want: %s\n+got: %s):\n", expectedResp.InstalledPackageDetail.Status.UserReason, actualResp.InstalledPackageDetail.Status.UserReason) - } - compareJSONStrings(t, expectedResp.InstalledPackageDetail.ValuesApplied, actualResp.InstalledPackageDetail.ValuesApplied) -} - func newRelease(meta metav1.ObjectMeta, spec *helmv2.HelmReleaseSpec, status *helmv2.HelmReleaseStatus) helmv2.HelmRelease { helmRelease := helmv2.HelmRelease{ ObjectMeta: meta, diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go index 93d8cf2edd6..bf09eb052e0 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go @@ -840,7 +840,9 @@ func (s *repoEventSink) indexAndEncode(checksum string, repo sourcev1.HelmReposi log.Errorf("Failed to read secret for repo due to: %+v", err) } else { fn := downloadHttpChartFn(opts) - if err = s.chartCache.SyncCharts(charts, fn); err != nil { + if err = s.chartCache.PurgeObsoleteChartVersions(charts); err != nil { + return nil, false, err + } else if err = s.chartCache.SyncCharts(charts, fn); err != nil { return nil, false, err } } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go index b76783fd2a7..c4a9cd634f5 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go @@ -816,18 +816,7 @@ func TestKindClusterGetPackageRepositorySummaries(t *testing.T) { return } - opts := cmpopts.IgnoreUnexported( - corev1.Context{}, - corev1.PackageRepositoryReference{}, - plugins.Plugin{}, - corev1.PackageRepositoryStatus{}, - corev1.GetPackageRepositorySummariesResponse{}, - corev1.PackageRepositorySummary{}, - ) - - if got, want := resp, tc.expectedResponse; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts)) - } + compareActualVsExpectedPackageRepositorySummaries(t, resp, tc.expectedResponse) }) } } @@ -1519,34 +1508,6 @@ func TestKindClusterAddTagsToOciRepository(t *testing.T) { }) } -func compareActualVsExpectedPackageRepositoryDetail(t *testing.T, actualDetail *corev1.GetPackageRepositoryDetailResponse, expectedDetail *corev1.GetPackageRepositoryDetailResponse) { - opts1 := cmpopts.IgnoreUnexported( - corev1.Context{}, - corev1.PackageRepositoryReference{}, - plugins.Plugin{}, - corev1.GetPackageRepositoryDetailResponse{}, - corev1.PackageRepositoryDetail{}, - corev1.PackageRepositoryStatus{}, - corev1.PackageRepositoryAuth{}, - corev1.PackageRepositoryTlsConfig{}, - corev1.SecretKeyReference{}, - corev1.UsernamePassword{}, - corev1.DockerCredentials{}, - ) - - opts2 := cmpopts.IgnoreFields(corev1.PackageRepositoryStatus{}, "UserReason") - - if got, want := actualDetail, expectedDetail; !cmp.Equal(want, got, opts1, opts2) { - t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts1, opts2)) - } - - if !strings.HasPrefix(actualDetail.GetDetail().Status.UserReason, expectedDetail.Detail.Status.UserReason) { - t.Errorf("unexpected response (status.UserReason): (-want +got):\n- %s\n+ %s", - expectedDetail.Detail.Status.UserReason, - actualDetail.GetDetail().Status.UserReason) - } -} - func setUserManagedSecrets(t *testing.T, fluxPluginReposClient v1alpha1.FluxV2RepositoriesServiceClient, value bool) bool { ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) defer cancel() diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go index 8b4b5839f0b..b8befdbbeb9 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go @@ -1728,22 +1728,7 @@ func TestGetPackageRepositoryDetail(t *testing.T) { if actualResp == nil { t.Fatalf("got: nil, want: response") } else { - opt1 := cmpopts.IgnoreUnexported( - corev1.Context{}, - corev1.PackageRepositoryReference{}, - plugins.Plugin{}, - corev1.GetPackageRepositoryDetailResponse{}, - corev1.PackageRepositoryDetail{}, - corev1.PackageRepositoryStatus{}, - corev1.PackageRepositoryAuth{}, - corev1.PackageRepositoryTlsConfig{}, - corev1.SecretKeyReference{}, - corev1.TlsCertKey{}, - corev1.UsernamePassword{}, - ) - if got, want := actualResp, tc.expectedResponse; !cmp.Equal(got, want, opt1) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1)) - } + compareActualVsExpectedPackageRepositoryDetail(t, actualResp, tc.expectedResponse) } } }) @@ -1908,18 +1893,7 @@ func TestGetPackageRepositorySummaries(t *testing.T) { return } - opts := cmpopts.IgnoreUnexported( - corev1.Context{}, - plugins.Plugin{}, - corev1.GetPackageRepositorySummariesResponse{}, - corev1.PackageRepositorySummary{}, - corev1.PackageRepositoryReference{}, - corev1.PackageRepositoryStatus{}, - ) - opts2 := cmpopts.SortSlices(lessPackageRepositorySummaryFunc) - if got, want := response, tc.expectedResponse; !cmp.Equal(want, got, opts, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2)) - } + compareActualVsExpectedPackageRepositorySummaries(t, response, tc.expectedResponse) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -2502,14 +2476,11 @@ func (s *Server) redisMockExpectGetFromRepoCache(mock redismock.ClientMock, filt } func (s *Server) redisMockSetValueForRepo(mock redismock.ClientMock, repo sourcev1.HelmRepository, oldValue []byte) (key string, bytes []byte, err error) { - backgroundClientGetter := &clientgetter.FixedClusterClientProvider{ClientsFunc: func(ctx context.Context) (*clientgetter.ClientGetter, error) { + bg := &clientgetter.FixedClusterClientProvider{ClientsFunc: func(ctx context.Context) (*clientgetter.ClientGetter, error) { return s.clientGetter.GetClients(ctx, s.kubeappsCluster) }} - sink := repoEventSink{ - clientGetter: backgroundClientGetter, - chartCache: nil, - } - return sink.redisMockSetValueForRepo(mock, repo, oldValue) + sinkNoCache := repoEventSink{clientGetter: bg} + return sinkNoCache.redisMockSetValueForRepo(mock, repo, oldValue) } func (sink *repoEventSink) redisMockSetValueForRepo(mock redismock.ClientMock, repo sourcev1.HelmRepository, oldValue []byte) (key string, newValue []byte, err error) { diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go index c23a303f04d..9cca4926943 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go @@ -5,6 +5,7 @@ package main import ( "context" + "fmt" "io" "strings" "testing" @@ -404,7 +405,10 @@ func newServer(t *testing.T, return s, mock, nil } -func seedRepoCacheWithRepos(t *testing.T, mock redismock.ClientMock, sink repoEventSink, repos []sourcev1.HelmRepository) map[string]sourcev1.HelmRepository { +func seedRepoCacheWithRepos(t *testing.T, + mock redismock.ClientMock, + sink repoEventSink, + repos []sourcev1.HelmRepository) map[string]sourcev1.HelmRepository { okRepos := make(map[string]sourcev1.HelmRepository) for _, r := range repos { key, err := redisKeyForRepo(r) @@ -430,7 +434,13 @@ func seedRepoCacheWithRepos(t *testing.T, mock redismock.ClientMock, sink repoEv return okRepos } -func seedChartCacheWithCharts(t *testing.T, redisCli *redis.Client, mock redismock.ClientMock, sink repoEventSink, stopCh <-chan struct{}, repos map[string]sourcev1.HelmRepository, charts []testSpecChartWithUrl) (*cache.ChartCache, func(), error) { +func seedChartCacheWithCharts(t *testing.T, + redisCli *redis.Client, + mock redismock.ClientMock, + sink repoEventSink, + stopCh <-chan struct{}, + repos map[string]sourcev1.HelmRepository, + charts []testSpecChartWithUrl) (*cache.ChartCache, func(), error) { t.Logf("+seedChartCacheWithCharts(%v)", charts) var chartCache *cache.ChartCache @@ -445,6 +455,8 @@ func seedChartCacheWithCharts(t *testing.T, redisCli *redis.Client, mock redismo } t.Cleanup(func() { chartCache.Shutdown() }) + uniqueRepoNames := map[types.NamespacedName]sets.Empty{} + // for now we only cache latest version of each chart for _, c := range charts { // very simple logic for now, relies on the order of elements in the array @@ -459,6 +471,13 @@ func seedChartCacheWithCharts(t *testing.T, redisCli *redis.Client, mock redismo Name: strings.Split(c.chartID, "/")[0], Namespace: c.repoNamespace} + _, ok := uniqueRepoNames[repoName] + if !ok { + uniqueRepoNames[repoName] = sets.Empty{} + match := fmt.Sprintf("helmcharts:%s:%s/*:*", repoName.Namespace, repoName.Name) + mock.ExpectScan(0, match, 0).SetVal([]string{}, 0) + } + repoKey, err := redisKeyForRepoNamespacedName(repoName) if err == nil { if r, ok := repos[repoKey]; ok { diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go index 3bb8dbbbace..97229647cde 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go @@ -13,11 +13,14 @@ import ( "net/http" "os" "reflect" + "sort" + "strings" "testing" helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" corev1 "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/packages/v1alpha1" plugins "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/plugins/v1alpha1" apiv1 "k8s.io/api/core/v1" @@ -111,11 +114,6 @@ func lessInstalledPackageSummaryFunc(p1, p2 *corev1.InstalledPackageSummary) boo return p1.Name < p2.Name } -// these are helpers to compare slices ignoring order -func lessPackageRepositorySummaryFunc(p1, p2 *corev1.PackageRepositorySummary) bool { - return p1.Name < p2.Name -} - func compareJSON(t *testing.T, expectedJSON, actualJSON *apiextv1.JSON) { expectedJSONString, actualJSONString := "", "" if expectedJSON != nil { @@ -392,6 +390,124 @@ func testCert(name string) string { return "./testdata/cert/" + name } +func compareActualVsExpectedAvailablePackageDetail(t *testing.T, actual *corev1.AvailablePackageDetail, expected *corev1.AvailablePackageDetail) { + opt1 := cmpopts.IgnoreUnexported( + corev1.AvailablePackageDetail{}, + corev1.AvailablePackageReference{}, + corev1.Context{}, + corev1.Maintainer{}, + plugins.Plugin{}, + corev1.PackageAppVersion{}) + // these few fields a bit special in that they are all very long strings, + // so we'll do a 'Contains' check for these instead of 'Equals' + opt2 := cmpopts.IgnoreFields(corev1.AvailablePackageDetail{}, "Readme", "DefaultValues", "ValuesSchema") + if got, want := actual, expected; !cmp.Equal(got, want, opt1, opt2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) + } + if !strings.Contains(actual.Readme, expected.Readme) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expected.Readme, actual.Readme) + } + if !strings.Contains(actual.DefaultValues, expected.DefaultValues) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expected.DefaultValues, actual.DefaultValues) + } + if !strings.Contains(actual.ValuesSchema, expected.ValuesSchema) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expected.ValuesSchema, actual.ValuesSchema) + } +} + +func compareActualVsExpectedPackageRepositorySummaries(t *testing.T, actualSummaries *corev1.GetPackageRepositorySummariesResponse, expectedSummaries *corev1.GetPackageRepositorySummariesResponse) { + opts := cmpopts.IgnoreUnexported( + corev1.Context{}, + corev1.PackageRepositoryReference{}, + plugins.Plugin{}, + corev1.PackageRepositoryStatus{}, + corev1.GetPackageRepositorySummariesResponse{}, + corev1.PackageRepositorySummary{}, + ) + + // will compare this separately below + opts2 := cmpopts.IgnoreFields(corev1.PackageRepositoryStatus{}, "UserReason") + + // cannot simply use cmpopts.SortSlices() due to doing a custom comparision of the UserReason field below. + // Also, we don't want side effects from in-line sorting so we make a copies and use it for comparison + // (same thing that cmp.Equal() does when you use cmpopts.SortSlices() option) + copyA := make([]*corev1.PackageRepositorySummary, len(actualSummaries.PackageRepositorySummaries)) + copy(copyA, actualSummaries.PackageRepositorySummaries) + sort.Slice(copyA, func(i, j int) bool { return copyA[i].Name < copyA[j].Name }) + + copyE := make([]*corev1.PackageRepositorySummary, len(expectedSummaries.PackageRepositorySummaries)) + copy(copyE, expectedSummaries.PackageRepositorySummaries) + sort.Slice(copyE, func(i, j int) bool { return copyE[i].Name < copyE[j].Name }) + + if got, want := copyA, copyE; !cmp.Equal(want, got, opts, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts, opts2)) + } + + // now compare UserReasons, mindful of the sort order + for i, s := range copyA { + if !strings.HasPrefix(s.Status.UserReason, copyE[i].Status.UserReason) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", + copyE[i].Status.UserReason, + s.Status.UserReason) + } + } +} + +func compareActualVsExpectedPackageRepositoryDetail(t *testing.T, actualDetail *corev1.GetPackageRepositoryDetailResponse, expectedDetail *corev1.GetPackageRepositoryDetailResponse) { + opts1 := cmpopts.IgnoreUnexported( + corev1.Context{}, + corev1.PackageRepositoryReference{}, + plugins.Plugin{}, + corev1.GetPackageRepositoryDetailResponse{}, + corev1.PackageRepositoryDetail{}, + corev1.PackageRepositoryStatus{}, + corev1.PackageRepositoryAuth{}, + corev1.PackageRepositoryTlsConfig{}, + corev1.SecretKeyReference{}, + corev1.UsernamePassword{}, + corev1.TlsCertKey{}, + corev1.DockerCredentials{}, + ) + + opts2 := cmpopts.IgnoreFields(corev1.PackageRepositoryStatus{}, "UserReason") + + if got, want := actualDetail, expectedDetail; !cmp.Equal(want, got, opts1, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts1, opts2)) + } + + if !strings.HasPrefix(actualDetail.GetDetail().Status.UserReason, expectedDetail.Detail.Status.UserReason) { + t.Fatalf("unexpected response (status.UserReason): (-want +got):\n- %s\n+ %s", + expectedDetail.Detail.Status.UserReason, + actualDetail.GetDetail().Status.UserReason) + } +} + +func compareActualVsExpectedGetInstalledPackageDetailResponse(t *testing.T, actualResp *corev1.GetInstalledPackageDetailResponse, expectedResp *corev1.GetInstalledPackageDetailResponse) { + opts := cmpopts.IgnoreUnexported( + corev1.GetInstalledPackageDetailResponse{}, + corev1.InstalledPackageDetail{}, + corev1.InstalledPackageReference{}, + corev1.Context{}, + corev1.VersionReference{}, + corev1.InstalledPackageStatus{}, + corev1.PackageAppVersion{}, + plugins.Plugin{}, + corev1.ReconciliationOptions{}, + corev1.AvailablePackageReference{}) + // see comment in release_integration_test.go. Intermittently we get an inconsistent error message from flux + opts2 := cmpopts.IgnoreFields(corev1.InstalledPackageStatus{}, "UserReason") + + // Values Applied are JSON string and need to be compared as such + opts3 := cmpopts.IgnoreFields(corev1.InstalledPackageDetail{}, "ValuesApplied") + if got, want := actualResp, expectedResp; !cmp.Equal(want, got, opts, opts2, opts3) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2, opts3)) + } + if !strings.Contains(actualResp.InstalledPackageDetail.Status.UserReason, expectedResp.InstalledPackageDetail.Status.UserReason) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expectedResp.InstalledPackageDetail.Status.UserReason, actualResp.InstalledPackageDetail.Status.UserReason) + } + compareJSONStrings(t, expectedResp.InstalledPackageDetail.ValuesApplied, actualResp.InstalledPackageDetail.ValuesApplied) +} + // misc global vars that get re-used in multiple tests var ( fluxPlugin = &plugins.Plugin{Name: "fluxv2.packages", Version: "v1alpha1"} From 1d27a8bfcabe1b76fb96a39be6b1c2609c317d99 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Tue, 15 Nov 2022 22:41:21 -0800 Subject: [PATCH 03/10] incremental --- .../v1alpha1/chart_integration_test.go | 48 +--- .../fluxv2/packages/v1alpha1/chart_test.go | 36 +-- .../packages/v1alpha1/global_vars_test.go | 207 +++++++++--------- .../v1alpha1/release_integration_test.go | 32 +-- .../fluxv2/packages/v1alpha1/release_test.go | 22 +- .../v1alpha1/repo_integration_test.go | 33 +-- .../fluxv2/packages/v1alpha1/repo_test.go | 130 ++--------- .../packages/v1alpha1/test_util_test.go | 86 ++++++-- 8 files changed, 222 insertions(+), 372 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go index f0f02e83e41..293ed076e4c 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_integration_test.go @@ -12,11 +12,8 @@ import ( "time" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" corev1 "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/packages/v1alpha1" - plugins "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/plugins/v1alpha1" fluxplugin "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/plugins/fluxv2/packages/v1alpha1" "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/common" "golang.org/x/sync/semaphore" @@ -854,22 +851,10 @@ func testKindClusterAvailablePackageEndpointsForOCIHelper( t.Fatal(err) } - opt1 := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageSummariesResponse{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, - plugins.Plugin{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) if !tc.unauthenticated { - if got, want := resp, expected_oci_stefanprodan_podinfo_available_summaries(repoName.Name); !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, resp, expected_oci_stefanprodan_podinfo_available_summaries(repoName.Name)) } else { - if got, want := resp, no_available_summaries(repoName.Name); !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, resp, no_available_summaries(repoName.Name)) return // nothing more to check } @@ -889,12 +874,7 @@ func testKindClusterAvailablePackageEndpointsForOCIHelper( if err != nil { t.Fatal(err) } - opts := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageVersionsResponse{}, - corev1.PackageAppVersion{}) - if got, want := resp2, expected_versions_stefanprodan_podinfo; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, resp2, expected_versions_stefanprodan_podinfo) hour, minute, second = time.Now().Clock() t.Logf("[%d:%d:%d] Calling GetAvailablePackageDetail(latest version) blocking for up to [%s]...", @@ -914,7 +894,7 @@ func testKindClusterAvailablePackageEndpointsForOCIHelper( t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, resp3.AvailablePackageDetail, expected_detail_oci_stefanprodan_podinfo(repoName.Name, tc.registryUrl).AvailablePackageDetail) @@ -936,7 +916,7 @@ func testKindClusterAvailablePackageEndpointsForOCIHelper( t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, resp4.AvailablePackageDetail, expected_detail_oci_stefanprodan_podinfo_2(repoName.Name, tc.registryUrl).AvailablePackageDetail) @@ -1026,17 +1006,7 @@ func TestKindClusterAvailablePackageEndpointsOCIRepo2Charts(t *testing.T) { t.Fatal(err) } - opt1 := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageSummariesResponse{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, - plugins.Plugin{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := resp, expected_oci_repo_with_2_charts_available_summaries(repoName.Name); !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, resp, expected_oci_repo_with_2_charts_available_summaries(repoName.Name)) }) } } @@ -1097,7 +1067,7 @@ func TestKindClusterAddRemovePackageVersionsInHttpRepo(t *testing.T) { time.Sleep(1 * time.Second) } } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, pkgDetail.AvailablePackageDetail, expected_detail_podinfo(repoName.Name, repoName.Namespace).AvailablePackageDetail) @@ -1131,7 +1101,7 @@ func TestKindClusterAddRemovePackageVersionsInHttpRepo(t *testing.T) { if err != nil { t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, pkgDetail.AvailablePackageDetail, expected_detail_podinfo_after_update_1(repoName.Name, repoName.Namespace).AvailablePackageDetail) @@ -1152,7 +1122,7 @@ func TestKindClusterAddRemovePackageVersionsInHttpRepo(t *testing.T) { if err != nil { t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, pkgDetail.AvailablePackageDetail, expected_detail_podinfo(repoName.Name, repoName.Namespace).AvailablePackageDetail) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go index c45b098bbf5..97eebfe2e11 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/chart_test.go @@ -16,8 +16,6 @@ import ( fluxmeta "github.com/fluxcd/pkg/apis/meta" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" redismock "github.com/go-redis/redismock/v8" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" corev1 "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/gen/core/packages/v1alpha1" "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache" "github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/common" @@ -232,7 +230,7 @@ func TestGetAvailablePackageDetail(t *testing.T) { t.Fatalf("%+v", err) } - compareActualVsExpectedAvailablePackageDetail(t, response.AvailablePackageDetail, tc.expectedPackageDetail) + compareAvailablePackageDetail(t, response.AvailablePackageDetail, tc.expectedPackageDetail) if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) @@ -327,7 +325,7 @@ func TestTransientHttpFailuresAreRetriedForChartCache(t *testing.T) { t.Fatalf("%+v", err) } - compareActualVsExpectedAvailablePackageDetail(t, + compareAvailablePackageDetail(t, response.AvailablePackageDetail, expected_detail_redis_1) if err = mock.ExpectationsWereMet(); err != nil { @@ -574,11 +572,8 @@ func TestNegativeGetAvailablePackageVersions(t *testing.T) { if tc.expectedStatusCode != codes.OK { return } + compareAvailablePackageVersions(t, response, tc.expectedResponse) - opts := cmpopts.IgnoreUnexported(corev1.GetAvailablePackageVersionsResponse{}, corev1.PackageAppVersion{}) - if got, want := response, tc.expectedResponse; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -684,13 +679,7 @@ func TestGetAvailablePackageVersions(t *testing.T) { if tc.expectedStatusCode != codes.OK { return } - - opts := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageVersionsResponse{}, - corev1.PackageAppVersion{}) - if got, want := response, tc.expectedResponse; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, response, tc.expectedResponse) }) } } @@ -778,13 +767,7 @@ func TestGetOciAvailablePackageVersions(t *testing.T) { if tc.expectedStatusCode != codes.OK { return } - - opts := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageVersionsResponse{}, - corev1.PackageAppVersion{}) - if got, want := response, tc.expectedResponse; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, response, tc.expectedResponse) }) } } @@ -1046,13 +1029,8 @@ func TestChartWithRelativeURL(t *testing.T) { if err != nil { t.Fatal(err) } - opts := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageVersionsResponse{}, - corev1.PackageAppVersion{}) - if got, want := response, expected_versions_airflow; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, response, expected_versions_airflow) if err = mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } @@ -1144,7 +1122,7 @@ func TestGetOciAvailablePackageDetail(t *testing.T) { t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail(t, response.AvailablePackageDetail, tc.expectedPackageDetail) + compareAvailablePackageDetail(t, response.AvailablePackageDetail, tc.expectedPackageDetail) if err = mock.ExpectationsWereMet(); err != nil { t.Fatal(err) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go index 15efe732373..f29f519f030 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go @@ -844,35 +844,37 @@ var ( }, } - valid_index_available_package_summaries = []*corev1.AvailablePackageSummary{ - { - Name: "acs-engine-autoscaler", - DisplayName: "acs-engine-autoscaler", - LatestVersion: pkgAppVersion("2.1.1"), - IconUrl: "https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png", - ShortDescription: "Scales worker nodes within agent pools", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "bitnami-1/acs-engine-autoscaler", - Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, - }, - Categories: []string{""}, - }, - { - Name: "wordpress", - DisplayName: "wordpress", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "0.7.5", - AppVersion: "4.9.1", + valid_index_available_package_summaries = &corev1.GetAvailablePackageSummariesResponse{ + AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ + { + Name: "acs-engine-autoscaler", + DisplayName: "acs-engine-autoscaler", + LatestVersion: pkgAppVersion("2.1.1"), + IconUrl: "https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png", + ShortDescription: "Scales worker nodes within agent pools", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "bitnami-1/acs-engine-autoscaler", + Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - IconUrl: "https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png", - ShortDescription: "new description!", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "bitnami-1/wordpress", - Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, + { + Name: "wordpress", + DisplayName: "wordpress", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "0.7.5", + AppVersion: "4.9.1", + }, + IconUrl: "https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png", + ShortDescription: "new description!", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "bitnami-1/wordpress", + Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - Categories: []string{""}, }, } @@ -929,70 +931,75 @@ var ( ghost_summary, } - index_before_update_summaries = []*corev1.AvailablePackageSummary{ - { - Name: "alpine", - DisplayName: "alpine", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "0.2.0", - }, - IconUrl: "", - ShortDescription: "Deploy a basic Alpine Linux pod", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "testrepo/alpine", - Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, - }, - Categories: []string{""}, - }, - { - Name: "nginx", - DisplayName: "nginx", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "1.1.0", + index_before_update_summaries = &corev1.GetAvailablePackageSummariesResponse{ + AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ + { + Name: "alpine", + DisplayName: "alpine", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "0.2.0", + }, + IconUrl: "", + ShortDescription: "Deploy a basic Alpine Linux pod", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "testrepo/alpine", + Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - IconUrl: "", - ShortDescription: "Create a basic nginx HTTP server", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "testrepo/nginx", - Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, + { + Name: "nginx", + DisplayName: "nginx", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "1.1.0", + }, + IconUrl: "", + ShortDescription: "Create a basic nginx HTTP server", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "testrepo/nginx", + Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - Categories: []string{""}, }, } - index_after_update_summaries = []*corev1.AvailablePackageSummary{ - { - Name: "alpine", - DisplayName: "alpine", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "0.3.0", + index_after_update_summaries = &corev1.GetAvailablePackageSummariesResponse{ + AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ + { + Name: "alpine", + DisplayName: "alpine", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "0.3.0", + }, + IconUrl: "", + ShortDescription: "Deploy a basic Alpine Linux pod", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "testrepo/alpine", + Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - IconUrl: "", - ShortDescription: "Deploy a basic Alpine Linux pod", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "testrepo/alpine", - Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, + { + Name: "nginx", + DisplayName: "nginx", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "1.1.0", + }, + IconUrl: "", + ShortDescription: "Create a basic nginx HTTP server", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "testrepo/nginx", + Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, + }, + Categories: []string{""}, }, - Categories: []string{""}, }, - { - Name: "nginx", - DisplayName: "nginx", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "1.1.0", - }, - IconUrl: "", - ShortDescription: "Create a basic nginx HTTP server", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "testrepo/nginx", - Context: &corev1.Context{Namespace: "ns2", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, - }, - Categories: []string{""}, - }} + } add_repo_1 = sourcev1.HelmRepository{ TypeMeta: metav1.TypeMeta{ @@ -2703,7 +2710,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: &corev1.PackageRepositoryAuth{PassCredentials: false}, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2750,7 +2757,7 @@ var ( }, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2770,7 +2777,7 @@ var ( CertAuthority: redactedString, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2828,7 +2835,7 @@ var ( }, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2842,7 +2849,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: tls_auth_redacted, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2860,7 +2867,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: secret_1_auth, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2874,7 +2881,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: foo_bar_auth_redacted, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2888,7 +2895,7 @@ var ( Url: podinfo_repo_url, Interval: "10m", Auth: &corev1.PackageRepositoryAuth{PassCredentials: false}, - Status: podinfo_repo_status_3, + Status: podinfo_repo_status_1, }, } @@ -3215,7 +3222,7 @@ var ( NamespaceScoped: false, Type: "helm", Url: "http://example.com", - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, RequiresAuth: true, } @@ -3226,7 +3233,7 @@ var ( NamespaceScoped: false, Type: "helm", Url: "http://example.com", - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, RequiresAuth: false, } @@ -3264,7 +3271,7 @@ var ( NamespaceScoped: false, Type: "helm", Url: podinfo_repo_url, - Status: podinfo_repo_status_3, + Status: podinfo_repo_status_1, RequiresAuth: false, } } @@ -3750,18 +3757,6 @@ var ( UserReason: "Succeeded: stored artifact for revision '", } - podinfo_repo_status_2 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '", - } - - podinfo_repo_status_3 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '", - } - podinfo_repo_status_4 = &corev1.PackageRepositoryStatus{ Ready: true, Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go index 50ebcc60a61..c14ea52fd9f 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go @@ -389,7 +389,7 @@ func TestKindClusterUpdateInstalledPackage(t *testing.T) { InstalledPackageDetail: tc.expectedDetailAfterUpdate, } - compareActualVsExpectedGetInstalledPackageDetailResponse(t, actualRespAfterUpdate, expectedResp) + compareInstalledPackageDetail(t, actualRespAfterUpdate, expectedResp) if tc.expectedRefsAfterUpdate != nil { expectedRefsCopy := []*corev1.ResourceRef{} @@ -485,7 +485,7 @@ func TestKindClusterAutoUpdateInstalledPackageFromHttpRepo(t *testing.T) { expected_detail_installed_package_auto_update_2.PostInstallationNotes, "@TARGET_NS@", spec.request.TargetContext.Namespace) - compareActualVsExpectedGetInstalledPackageDetailResponse( + compareInstalledPackageDetail( t, resp, &corev1.GetInstalledPackageDetailResponse{ InstalledPackageDetail: expected_detail_installed_package_auto_update_2, }) @@ -566,7 +566,7 @@ func TestKindClusterAutoUpdateInstalledPackageFromOciRepo(t *testing.T) { expected_detail_installed_package_auto_update_oci_2.PostInstallationNotes, "@TARGET_NS@", spec.request.TargetContext.Namespace) - compareActualVsExpectedGetInstalledPackageDetailResponse( + compareInstalledPackageDetail( t, resp, &corev1.GetInstalledPackageDetailResponse{ InstalledPackageDetail: expected_detail_installed_package_auto_update_oci_2, }) @@ -871,7 +871,7 @@ func TestKindClusterRBAC_ReadRelease(t *testing.T) { expected_detail_test_release_rbac_2.InstalledPackageDetail.PostInstallationNotes, "@TARGET_NS@", ns2) expected_detail_test_release_rbac_2.InstalledPackageDetail.AvailablePackageRef.Context.Namespace = ns1 - compareActualVsExpectedGetInstalledPackageDetailResponse(t, resp2, expected_detail_test_release_rbac_2) + compareInstalledPackageDetail(t, resp2, expected_detail_test_release_rbac_2) } grpcCtx, cancel = context.WithTimeout(grpcCtxAdmin, defaultContextTimeout) @@ -1001,24 +1001,12 @@ func TestKindClusterRBAC_ReadRelease(t *testing.T) { }, }) - opts2 := cmpopts.IgnoreUnexported( - corev1.GetInstalledPackageSummariesResponse{}, - corev1.InstalledPackageSummary{}, - corev1.InstalledPackageReference{}, - corev1.InstalledPackageStatus{}, - plugins.Plugin{}, - corev1.VersionReference{}, - corev1.PackageAppVersion{}, - corev1.Context{}) - if err != nil { t.Fatal(err) } else { // should return installed package summaries without chart details expected_summaries_test_release_rbac_1.InstalledPackageSummaries[0].InstalledPackageRef.Context.Namespace = ns2 - if got, want := resp, expected_summaries_test_release_rbac_1; !cmp.Equal(want, got, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts2)) - } + compareInstalledPackageSummaries(t, resp, expected_summaries_test_release_rbac_1) } grpcCtx, cancel = context.WithTimeout(grpcCtxReadHelmReleases, defaultContextTimeout) @@ -1032,7 +1020,7 @@ func TestKindClusterRBAC_ReadRelease(t *testing.T) { if err != nil { t.Fatal(err) } else { - compareActualVsExpectedGetInstalledPackageDetailResponse(t, resp2, expected_detail_test_release_rbac_2) + compareInstalledPackageDetail(t, resp2, expected_detail_test_release_rbac_2) } grpcCtx, cancel = context.WithTimeout(grpcCtxReadHelmReleases, defaultContextTimeout) @@ -1108,9 +1096,7 @@ func TestKindClusterRBAC_ReadRelease(t *testing.T) { } else { // should return installed package summaries with chart details expected_summaries_test_release_rbac_2.InstalledPackageSummaries[0].InstalledPackageRef.Context.Namespace = ns2 - if got, want := resp, expected_summaries_test_release_rbac_2; !cmp.Equal(want, got, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts2)) - } + compareInstalledPackageSummaries(t, resp, expected_summaries_test_release_rbac_2) } grpcCtx, cancel = context.WithTimeout(grpcCtxReadHelmReleasesAndCharts, defaultContextTimeout) @@ -1124,7 +1110,7 @@ func TestKindClusterRBAC_ReadRelease(t *testing.T) { if err != nil { t.Fatal(err) } else { - compareActualVsExpectedGetInstalledPackageDetailResponse(t, resp2, expected_detail_test_release_rbac_2) + compareInstalledPackageDetail(t, resp2, expected_detail_test_release_rbac_2) } } @@ -1688,7 +1674,7 @@ func createAndWaitForHelmRelease( InstalledPackageDetail: tc.expectedDetail, } - compareActualVsExpectedGetInstalledPackageDetailResponse(t, actualDetailResp, expectedResp) + compareInstalledPackageDetail(t, actualDetailResp, expectedResp) if !tc.expectInstallFailure { // check artifacts in target namespace: diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go index 0b09b705483..2d790ec86a6 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go @@ -245,20 +245,7 @@ func TestGetInstalledPackageSummariesWithoutPagination(t *testing.T) { if tc.expectedStatusCode != codes.OK { return } - - opts := cmpopts.IgnoreUnexported( - corev1.GetInstalledPackageSummariesResponse{}, - corev1.InstalledPackageSummary{}, - corev1.InstalledPackageReference{}, - corev1.Context{}, - corev1.VersionReference{}, - corev1.InstalledPackageStatus{}, - corev1.PackageAppVersion{}, - plugins.Plugin{}) - opts2 := cmpopts.SortSlices(lessInstalledPackageSummaryFunc) - if got, want := response, tc.expectedResponse; !cmp.Equal(want, got, opts, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2)) - } + compareInstalledPackageSummaries(t, response, tc.expectedResponse) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -383,9 +370,8 @@ func TestGetInstalledPackageSummariesWithPagination(t *testing.T) { if got, want := status.Code(err), codes.OK; got != want { t.Fatalf("got: %+v, want: %+v, err: %+v", got, want, err) } - if got, want := response3, nextExpectedResp; !cmp.Equal(want, got, opts, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2)) - } + + compareInstalledPackageSummaries(t, response3, nextExpectedResp) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -505,7 +491,7 @@ func TestGetInstalledPackageDetail(t *testing.T) { InstalledPackageDetail: tc.expectedDetail, } - compareActualVsExpectedGetInstalledPackageDetailResponse(t, response, expectedResp) + compareInstalledPackageDetail(t, response, expectedResp) // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go index c4a9cd634f5..70a533966f8 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go @@ -126,17 +126,7 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { }, }) if err == nil { - opt1 := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageSummariesResponse{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, - plugins.Plugin{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := resp, available_package_summaries_podinfo_basic_auth(repoName.Name); !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, resp, available_package_summaries_podinfo_basic_auth(repoName.Name)) break } else if i == maxWait { t.Fatalf("Timed out waiting for available package summaries, last response: %v, last error: [%v]", resp, err) @@ -179,7 +169,7 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { t.Fatalf("%v", err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, resp.AvailablePackageDetail, expected_detail_podinfo_basic_auth(repoName.Name).AvailablePackageDetail) @@ -614,7 +604,7 @@ func TestKindClusterGetPackageRepositoryDetail(t *testing.T) { time.Sleep(2 * time.Second) } } - compareActualVsExpectedPackageRepositoryDetail(t, resp, tc.expectedResponse) + comparePackageRepositoryDetail(t, resp, tc.expectedResponse) }) } } @@ -816,7 +806,7 @@ func TestKindClusterGetPackageRepositorySummaries(t *testing.T) { return } - compareActualVsExpectedPackageRepositorySummaries(t, resp, tc.expectedResponse) + comparePackageRepositorySummaries(t, resp, tc.expectedResponse) }) } } @@ -1058,7 +1048,7 @@ func TestKindClusterUpdatePackageRepository(t *testing.T) { actualDetail := waitForRepoToReconcileWithSuccess( t, fluxPluginReposClient, grpcCtx, tc.repoName, repoNamespace) - compareActualVsExpectedPackageRepositoryDetail(t, actualDetail, tc.expectedDetail) + comparePackageRepositoryDetail(t, actualDetail, tc.expectedDetail) }) } } @@ -1397,7 +1387,7 @@ func TestKindClusterUpdatePackageRepoSecretUnchanged(t *testing.T) { actualDetail := waitForRepoToReconcileWithSuccess( t, fluxPluginReposClient, grpcAdmin, repoName, repoNamespace) - compareActualVsExpectedPackageRepositoryDetail(t, actualDetail, expectedDetail) + comparePackageRepositoryDetail(t, actualDetail, expectedDetail) } func TestKindClusterAddTagsToOciRepository(t *testing.T) { @@ -1465,12 +1455,7 @@ func TestKindClusterAddTagsToOciRepository(t *testing.T) { if err != nil { t.Fatal(err) } - opts := cmpopts.IgnoreUnexported( - corev1.GetAvailablePackageVersionsResponse{}, - corev1.PackageAppVersion{}) - if got, want := resp2, expected_versions_gfichtenholt_podinfo; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, resp2, expected_versions_gfichtenholt_podinfo) if err = helmPushChartToMyGithubRegistry(t, "6.1.6"); err != nil { t.Fatal(err) @@ -1502,9 +1487,7 @@ func TestKindClusterAddTagsToOciRepository(t *testing.T) { if err != nil { t.Fatal(err) } - if got, want := resp3, expected_versions_gfichtenholt_podinfo_3; !cmp.Equal(want, got, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts)) - } + compareAvailablePackageVersions(t, resp3, expected_versions_gfichtenholt_podinfo_3) }) } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go index b8befdbbeb9..20e9ef9267d 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go @@ -57,10 +57,8 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { index: testYaml("valid-index.yaml"), }, }, - request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}, - expectedResponse: &corev1.GetAvailablePackageSummariesResponse{ - AvailablePackageSummaries: valid_index_available_package_summaries, - }, + request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}, + expectedResponse: valid_index_available_package_summaries, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request namespace is specified)", @@ -72,10 +70,8 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { index: testYaml("valid-index.yaml"), }, }, - request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "default"}}, - expectedResponse: &corev1.GetAvailablePackageSummariesResponse{ - AvailablePackageSummaries: valid_index_available_package_summaries, - }, + request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "default"}}, + expectedResponse: valid_index_available_package_summaries, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request cluster is specified and matches the kubeapps cluster)", @@ -91,9 +87,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { Cluster: KubeappsCluster, Namespace: "default", }}, - expectedResponse: &corev1.GetAvailablePackageSummariesResponse{ - AvailablePackageSummaries: valid_index_available_package_summaries, - }, + expectedResponse: valid_index_available_package_summaries, }, { name: "it returns all fluxv2 packages from the cluster (when request namespace is does not match repo namespace)", @@ -111,10 +105,8 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { index: testYaml("jetstack-index.yaml"), }, }, - request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "non-default"}}, - expectedResponse: &corev1.GetAvailablePackageSummariesResponse{ - AvailablePackageSummaries: append(valid_index_available_package_summaries, cert_manager_summary), - }, + request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "non-default"}}, + expectedResponse: valid_index_available_package_summaries, }, { name: "uses a filter based on existing repo", @@ -461,12 +453,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) } - - opt1 := cmpopts.IgnoreUnexported(corev1.GetAvailablePackageSummariesResponse{}, corev1.AvailablePackageSummary{}, corev1.AvailablePackageReference{}, corev1.Context{}, plugins.Plugin{}, corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := response, tc.expectedResponse; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) } } @@ -571,9 +558,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { if got, want := status.Code(err), codes.OK; got != want { t.Fatalf("got: %v, want: %v", err, want) } - if got, want := response2, nextExpectedResp; !cmp.Equal(want, got, opts, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2)) - } + compareAvailablePackageSummaries(t, response2, nextExpectedResp) request3 := &corev1.GetAvailablePackageSummariesRequest{ Context: &corev1.Context{Namespace: "blah"}, @@ -593,9 +578,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { if got, want := status.Code(err), codes.OK; got != want { t.Fatalf("got: %v, want: %v", err, want) } - if got, want := response3, nextExpectedResp; !cmp.Equal(want, got, opts, opts2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2)) - } + compareAvailablePackageSummaries(t, response3, nextExpectedResp) if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) @@ -671,18 +654,7 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { t.Fatalf("%v", err) } - opt1 := cmpopts.IgnoreUnexported( - corev1.AvailablePackageDetail{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, - plugins.Plugin{}, - corev1.Maintainer{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := responseBeforeUpdate.AvailablePackageSummaries, index_before_update_summaries; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, responseBeforeUpdate, index_before_update_summaries) // see below key, oldValue, err := s.redisKeyValueForRepo(repo) @@ -729,10 +701,7 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { if err != nil { t.Fatalf("%v", err) } - - if got, want := responsePackagesAfterUpdate.AvailablePackageSummaries, index_after_update_summaries; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, responsePackagesAfterUpdate, index_after_update_summaries) if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) @@ -801,18 +770,7 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { t.Fatalf("%v", err) } - opt1 := cmpopts.IgnoreUnexported( - corev1.AvailablePackageDetail{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, - plugins.Plugin{}, - corev1.Maintainer{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := responseBeforeDelete.AvailablePackageSummaries, valid_index_available_package_summaries; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, responseBeforeDelete, valid_index_available_package_summaries) // now we are going to simulate the user deleting a HelmRepository CR which, in turn, // causes k8s server to fire a DELETE event @@ -902,17 +860,7 @@ func TestGetAvailablePackageSummaryAfterCacheResync(t *testing.T) { t.Fatalf("%v", err) } - opt1 := cmpopts.IgnoreUnexported( - corev1.AvailablePackageDetail{}, - corev1.AvailablePackageSummary{}, - corev1.AvailablePackageReference{}, - corev1.Context{}, plugins.Plugin{}, - corev1.Maintainer{}, - corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := responseBeforeResync.AvailablePackageSummaries, valid_index_available_package_summaries; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, responseBeforeResync, valid_index_available_package_summaries) resyncCh, err := s.repoCache.ExpectResync() if err != nil { @@ -962,9 +910,7 @@ func TestGetAvailablePackageSummaryAfterCacheResync(t *testing.T) { t.Fatalf("%v", err) } - if got, want := responseAfterResync.AvailablePackageSummaries, valid_index_available_package_summaries; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, responseAfterResync, valid_index_available_package_summaries) }) } @@ -1728,7 +1674,7 @@ func TestGetPackageRepositoryDetail(t *testing.T) { if actualResp == nil { t.Fatalf("got: nil, want: response") } else { - compareActualVsExpectedPackageRepositoryDetail(t, actualResp, tc.expectedResponse) + comparePackageRepositoryDetail(t, actualResp, tc.expectedResponse) } } }) @@ -1787,26 +1733,10 @@ func TestGetOciPackageRepositoryDetail(t *testing.T) { if actualResp == nil { t.Fatalf("got: nil, want: response") } else { - opt1 := cmpopts.IgnoreUnexported( - corev1.Context{}, - corev1.PackageRepositoryReference{}, - plugins.Plugin{}, - corev1.GetPackageRepositoryDetailResponse{}, - corev1.PackageRepositoryDetail{}, - corev1.PackageRepositoryStatus{}, - corev1.PackageRepositoryAuth{}, - corev1.PackageRepositoryTlsConfig{}, - corev1.SecretKeyReference{}, - corev1.TlsCertKey{}, - corev1.UsernamePassword{}, - ) - if got, want := actualResp, tc.expectedResponse; !cmp.Equal(got, want, opt1) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1)) - } + comparePackageRepositoryDetail(t, actualResp, tc.expectedResponse) } } - // FWIW GetPackageRepositoryDetail currently does not use the redis cache if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) } @@ -1893,7 +1823,7 @@ func TestGetPackageRepositorySummaries(t *testing.T) { return } - compareActualVsExpectedPackageRepositorySummaries(t, response, tc.expectedResponse) + comparePackageRepositorySummaries(t, response, tc.expectedResponse) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -2148,22 +2078,7 @@ func TestUpdatePackageRepository(t *testing.T) { if actualDetail == nil { t.Fatalf("got: nil, want: detail") } else { - opt1 := cmpopts.IgnoreUnexported( - corev1.Context{}, - corev1.PackageRepositoryReference{}, - plugins.Plugin{}, - corev1.GetPackageRepositoryDetailResponse{}, - corev1.PackageRepositoryDetail{}, - corev1.PackageRepositoryStatus{}, - corev1.PackageRepositoryAuth{}, - corev1.PackageRepositoryTlsConfig{}, - corev1.SecretKeyReference{}, - corev1.TlsCertKey{}, - corev1.UsernamePassword{}, - ) - if got, want := actualDetail, tc.expectedDetail; !cmp.Equal(got, want, opt1) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1)) - } + comparePackageRepositoryDetail(t, actualDetail, tc.expectedDetail) } if !tc.userManagedSecrets && tc.oldRepoSecret != nil && actualDetail.Detail.Auth.Type == corev1.PackageRepositoryAuth_PACKAGE_REPOSITORY_AUTH_TYPE_UNSPECIFIED { @@ -2384,12 +2299,7 @@ func TestGetOciAvailablePackageSummariesWithoutPagination(t *testing.T) { if err = mock.ExpectationsWereMet(); err != nil { t.Fatalf("%v", err) } - - opt1 := cmpopts.IgnoreUnexported(corev1.GetAvailablePackageSummariesResponse{}, corev1.AvailablePackageSummary{}, corev1.AvailablePackageReference{}, corev1.Context{}, plugins.Plugin{}, corev1.PackageAppVersion{}) - opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) - if got, want := response, tc.expectedResponse; !cmp.Equal(got, want, opt1, opt2) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) - } + compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) } } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go index 97229647cde..a9c951ee0c6 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go @@ -390,7 +390,7 @@ func testCert(name string) string { return "./testdata/cert/" + name } -func compareActualVsExpectedAvailablePackageDetail(t *testing.T, actual *corev1.AvailablePackageDetail, expected *corev1.AvailablePackageDetail) { +func compareAvailablePackageDetail(t *testing.T, actual *corev1.AvailablePackageDetail, expected *corev1.AvailablePackageDetail) { opt1 := cmpopts.IgnoreUnexported( corev1.AvailablePackageDetail{}, corev1.AvailablePackageReference{}, @@ -401,8 +401,8 @@ func compareActualVsExpectedAvailablePackageDetail(t *testing.T, actual *corev1. // these few fields a bit special in that they are all very long strings, // so we'll do a 'Contains' check for these instead of 'Equals' opt2 := cmpopts.IgnoreFields(corev1.AvailablePackageDetail{}, "Readme", "DefaultValues", "ValuesSchema") - if got, want := actual, expected; !cmp.Equal(got, want, opt1, opt2) { - t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opt1, opt2)) + if !cmp.Equal(actual, expected, opt1, opt2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(actual, expected, opt1, opt2)) } if !strings.Contains(actual.Readme, expected.Readme) { t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expected.Readme, actual.Readme) @@ -415,7 +415,7 @@ func compareActualVsExpectedAvailablePackageDetail(t *testing.T, actual *corev1. } } -func compareActualVsExpectedPackageRepositorySummaries(t *testing.T, actualSummaries *corev1.GetPackageRepositorySummariesResponse, expectedSummaries *corev1.GetPackageRepositorySummariesResponse) { +func comparePackageRepositorySummaries(t *testing.T, actual *corev1.GetPackageRepositorySummariesResponse, expected *corev1.GetPackageRepositorySummariesResponse) { opts := cmpopts.IgnoreUnexported( corev1.Context{}, corev1.PackageRepositoryReference{}, @@ -431,16 +431,16 @@ func compareActualVsExpectedPackageRepositorySummaries(t *testing.T, actualSumma // cannot simply use cmpopts.SortSlices() due to doing a custom comparision of the UserReason field below. // Also, we don't want side effects from in-line sorting so we make a copies and use it for comparison // (same thing that cmp.Equal() does when you use cmpopts.SortSlices() option) - copyA := make([]*corev1.PackageRepositorySummary, len(actualSummaries.PackageRepositorySummaries)) - copy(copyA, actualSummaries.PackageRepositorySummaries) + copyA := make([]*corev1.PackageRepositorySummary, len(actual.PackageRepositorySummaries)) + copy(copyA, actual.PackageRepositorySummaries) sort.Slice(copyA, func(i, j int) bool { return copyA[i].Name < copyA[j].Name }) - copyE := make([]*corev1.PackageRepositorySummary, len(expectedSummaries.PackageRepositorySummaries)) - copy(copyE, expectedSummaries.PackageRepositorySummaries) + copyE := make([]*corev1.PackageRepositorySummary, len(expected.PackageRepositorySummaries)) + copy(copyE, expected.PackageRepositorySummaries) sort.Slice(copyE, func(i, j int) bool { return copyE[i].Name < copyE[j].Name }) - if got, want := copyA, copyE; !cmp.Equal(want, got, opts, opts2) { - t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts, opts2)) + if !cmp.Equal(copyA, copyE, opts, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(copyE, copyA, opts, opts, opts2)) } // now compare UserReasons, mindful of the sort order @@ -453,7 +453,7 @@ func compareActualVsExpectedPackageRepositorySummaries(t *testing.T, actualSumma } } -func compareActualVsExpectedPackageRepositoryDetail(t *testing.T, actualDetail *corev1.GetPackageRepositoryDetailResponse, expectedDetail *corev1.GetPackageRepositoryDetailResponse) { +func comparePackageRepositoryDetail(t *testing.T, actual *corev1.GetPackageRepositoryDetailResponse, expected *corev1.GetPackageRepositoryDetailResponse) { opts1 := cmpopts.IgnoreUnexported( corev1.Context{}, corev1.PackageRepositoryReference{}, @@ -471,18 +471,18 @@ func compareActualVsExpectedPackageRepositoryDetail(t *testing.T, actualDetail * opts2 := cmpopts.IgnoreFields(corev1.PackageRepositoryStatus{}, "UserReason") - if got, want := actualDetail, expectedDetail; !cmp.Equal(want, got, opts1, opts2) { - t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts1, opts2)) + if !cmp.Equal(expected, actual, opts1, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts1, opts2)) } - if !strings.HasPrefix(actualDetail.GetDetail().Status.UserReason, expectedDetail.Detail.Status.UserReason) { + if !strings.HasPrefix(actual.GetDetail().Status.UserReason, expected.Detail.Status.UserReason) { t.Fatalf("unexpected response (status.UserReason): (-want +got):\n- %s\n+ %s", - expectedDetail.Detail.Status.UserReason, - actualDetail.GetDetail().Status.UserReason) + expected.Detail.Status.UserReason, + actual.GetDetail().Status.UserReason) } } -func compareActualVsExpectedGetInstalledPackageDetailResponse(t *testing.T, actualResp *corev1.GetInstalledPackageDetailResponse, expectedResp *corev1.GetInstalledPackageDetailResponse) { +func compareInstalledPackageDetail(t *testing.T, actual *corev1.GetInstalledPackageDetailResponse, expected *corev1.GetInstalledPackageDetailResponse) { opts := cmpopts.IgnoreUnexported( corev1.GetInstalledPackageDetailResponse{}, corev1.InstalledPackageDetail{}, @@ -499,13 +499,55 @@ func compareActualVsExpectedGetInstalledPackageDetailResponse(t *testing.T, actu // Values Applied are JSON string and need to be compared as such opts3 := cmpopts.IgnoreFields(corev1.InstalledPackageDetail{}, "ValuesApplied") - if got, want := actualResp, expectedResp; !cmp.Equal(want, got, opts, opts2, opts3) { - t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts, opts2, opts3)) + if !cmp.Equal(expected, actual, opts, opts2, opts3) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts, opts2, opts3)) } - if !strings.Contains(actualResp.InstalledPackageDetail.Status.UserReason, expectedResp.InstalledPackageDetail.Status.UserReason) { - t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expectedResp.InstalledPackageDetail.Status.UserReason, actualResp.InstalledPackageDetail.Status.UserReason) + if !strings.Contains(actual.InstalledPackageDetail.Status.UserReason, expected.InstalledPackageDetail.Status.UserReason) { + t.Fatalf("substring mismatch (-want: %s\n+got: %s):\n", expected.InstalledPackageDetail.Status.UserReason, actual.InstalledPackageDetail.Status.UserReason) + } + compareJSONStrings(t, expected.InstalledPackageDetail.ValuesApplied, actual.InstalledPackageDetail.ValuesApplied) +} + +func compareAvailablePackageSummaries(t *testing.T, actual *corev1.GetAvailablePackageSummariesResponse, expected *corev1.GetAvailablePackageSummariesResponse) { + opt1 := cmpopts.IgnoreUnexported( + corev1.GetAvailablePackageSummariesResponse{}, + corev1.AvailablePackageSummary{}, + corev1.AvailablePackageReference{}, + corev1.Context{}, + plugins.Plugin{}, + corev1.PackageAppVersion{}) + opt2 := cmpopts.SortSlices(lessAvailablePackageFunc) + + if !cmp.Equal(actual, expected, opt1, opt2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opt1, opt2)) + } +} + +func compareAvailablePackageVersions(t *testing.T, actual *corev1.GetAvailablePackageVersionsResponse, expected *corev1.GetAvailablePackageVersionsResponse) { + opts := cmpopts.IgnoreUnexported( + corev1.GetAvailablePackageVersionsResponse{}, + corev1.PackageAppVersion{}) + if !cmp.Equal(expected, actual, opts) { + t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts)) + } +} + +func compareInstalledPackageSummaries(t *testing.T, actual *corev1.GetInstalledPackageSummariesResponse, expected *corev1.GetInstalledPackageSummariesResponse) { + opts := cmpopts.SortSlices(lessInstalledPackageSummaryFunc) + + opts2 := cmpopts.IgnoreUnexported( + corev1.GetInstalledPackageSummariesResponse{}, + corev1.InstalledPackageSummary{}, + corev1.InstalledPackageReference{}, + corev1.InstalledPackageStatus{}, + plugins.Plugin{}, + corev1.VersionReference{}, + corev1.PackageAppVersion{}, + corev1.Context{}) + + if !cmp.Equal(expected, actual, opts, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts, opts2)) } - compareJSONStrings(t, expectedResp.InstalledPackageDetail.ValuesApplied, actualResp.InstalledPackageDetail.ValuesApplied) } // misc global vars that get re-used in multiple tests From 40d40167385f93a30ebfb2db8686c27fc7fb3131 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Wed, 16 Nov 2022 00:37:16 -0800 Subject: [PATCH 04/10] incremental --- .../packages/v1alpha1/global_vars_test.go | 64 +++++------ .../fluxv2/packages/v1alpha1/repo_test.go | 100 +++++++++--------- .../packages/v1alpha1/test_util_test.go | 2 +- 3 files changed, 85 insertions(+), 81 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go index f29f519f030..d12c88ed7dd 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go @@ -844,40 +844,42 @@ var ( }, } - valid_index_available_package_summaries = &corev1.GetAvailablePackageSummariesResponse{ - AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ - { - Name: "acs-engine-autoscaler", - DisplayName: "acs-engine-autoscaler", - LatestVersion: pkgAppVersion("2.1.1"), - IconUrl: "https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png", - ShortDescription: "Scales worker nodes within agent pools", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "bitnami-1/acs-engine-autoscaler", - Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, - }, - Categories: []string{""}, + valid_index_available_package_summaries = []*corev1.AvailablePackageSummary{ + { + Name: "acs-engine-autoscaler", + DisplayName: "acs-engine-autoscaler", + LatestVersion: pkgAppVersion("2.1.1"), + IconUrl: "https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png", + ShortDescription: "Scales worker nodes within agent pools", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "bitnami-1/acs-engine-autoscaler", + Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, }, - { - Name: "wordpress", - DisplayName: "wordpress", - LatestVersion: &corev1.PackageAppVersion{ - PkgVersion: "0.7.5", - AppVersion: "4.9.1", - }, - IconUrl: "https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png", - ShortDescription: "new description!", - AvailablePackageRef: &corev1.AvailablePackageReference{ - Identifier: "bitnami-1/wordpress", - Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, - Plugin: fluxPlugin, - }, - Categories: []string{""}, + Categories: []string{""}, + }, + { + Name: "wordpress", + DisplayName: "wordpress", + LatestVersion: &corev1.PackageAppVersion{ + PkgVersion: "0.7.5", + AppVersion: "4.9.1", + }, + IconUrl: "https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png", + ShortDescription: "new description!", + AvailablePackageRef: &corev1.AvailablePackageReference{ + Identifier: "bitnami-1/wordpress", + Context: &corev1.Context{Namespace: "default", Cluster: KubeappsCluster}, + Plugin: fluxPlugin, }, + Categories: []string{""}, }, } + valid_index_available_package_summaries_resp = &corev1.GetAvailablePackageSummariesResponse{ + AvailablePackageSummaries: valid_index_available_package_summaries, + } + cert_manager_summary = &corev1.AvailablePackageSummary{ Name: "cert-manager", DisplayName: "cert-manager", @@ -931,7 +933,7 @@ var ( ghost_summary, } - index_before_update_summaries = &corev1.GetAvailablePackageSummariesResponse{ + expected_summaries_before_update = &corev1.GetAvailablePackageSummariesResponse{ AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ { Name: "alpine", @@ -966,7 +968,7 @@ var ( }, } - index_after_update_summaries = &corev1.GetAvailablePackageSummariesResponse{ + expected_summaries_after_update = &corev1.GetAvailablePackageSummariesResponse{ AvailablePackageSummaries: []*corev1.AvailablePackageSummary{ { Name: "alpine", diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go index 20e9ef9267d..f7c027e13c9 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go @@ -58,7 +58,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { }, }, request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}, - expectedResponse: valid_index_available_package_summaries, + expectedResponse: valid_index_available_package_summaries_resp, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request namespace is specified)", @@ -71,7 +71,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { }, }, request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "default"}}, - expectedResponse: valid_index_available_package_summaries, + expectedResponse: valid_index_available_package_summaries_resp, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request cluster is specified and matches the kubeapps cluster)", @@ -87,7 +87,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { Cluster: KubeappsCluster, Namespace: "default", }}, - expectedResponse: valid_index_available_package_summaries, + expectedResponse: valid_index_available_package_summaries_resp, }, { name: "it returns all fluxv2 packages from the cluster (when request namespace is does not match repo namespace)", @@ -105,8 +105,10 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { index: testYaml("jetstack-index.yaml"), }, }, - request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "non-default"}}, - expectedResponse: valid_index_available_package_summaries, + request: &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{Namespace: "non-default"}}, + expectedResponse: &corev1.GetAvailablePackageSummariesResponse{ + AvailablePackageSummaries: append(valid_index_available_package_summaries, cert_manager_summary), + }, }, { name: "uses a filter based on existing repo", @@ -430,13 +432,13 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { for _, r := range repos { if r.Namespace == tc.request.Context.Namespace { if err = s.redisMockExpectGetFromRepoCache(mock, nil, r); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } } } else { if err = s.redisMockExpectGetFromRepoCache(mock, tc.request.FilterOptions, repos...); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } @@ -451,7 +453,7 @@ func TestGetAvailablePackageSummariesWithoutPagination(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) @@ -490,7 +492,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, nil, repos...); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } request1 := &corev1.GetAvailablePackageSummariesRequest{ @@ -552,7 +554,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, nil, repos...); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } response2, err := s.GetAvailablePackageSummaries(context.Background(), request2) if got, want := status.Code(err), codes.OK; got != want { @@ -572,7 +574,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { NextPageToken: "", } if err = s.redisMockExpectGetFromRepoCache(mock, nil, repos...); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } response3, err := s.GetAvailablePackageSummaries(context.Background(), request3) if got, want := status.Code(err), codes.OK; got != want { @@ -581,7 +583,7 @@ func TestGetAvailablePackageSummariesWithPagination(t *testing.T) { compareAvailablePackageSummaries(t, response3, nextExpectedResp) if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -639,7 +641,7 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, nil, repo); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } ctx := context.Background() @@ -647,19 +649,19 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { ctx, &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareAvailablePackageSummaries(t, responseBeforeUpdate, index_before_update_summaries) + compareAvailablePackageSummaries(t, responseBeforeUpdate, expected_summaries_before_update) // see below key, oldValue, err := s.redisKeyValueForRepo(repo) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } ctrlClient, _, err := ctrlClientAndWatcher(t, s) @@ -690,7 +692,7 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { s.repoCache.WaitUntilForgotten(key) if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } mock.ExpectGet(key).SetVal(string(newValue)) @@ -699,12 +701,12 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { ctx, &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareAvailablePackageSummaries(t, responsePackagesAfterUpdate, index_after_update_summaries) + compareAvailablePackageSummaries(t, responsePackagesAfterUpdate, expected_summaries_after_update) if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } }) @@ -756,21 +758,21 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, nil, *repo); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } responseBeforeDelete, err := s.GetAvailablePackageSummaries( context.Background(), &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareAvailablePackageSummaries(t, responseBeforeDelete, valid_index_available_package_summaries) + compareAvailablePackageSummaries(t, responseBeforeDelete, valid_index_available_package_summaries_resp) // now we are going to simulate the user deleting a HelmRepository CR which, in turn, // causes k8s server to fire a DELETE event @@ -781,11 +783,11 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { repoKey, err := redisKeyForRepoNamespacedName(repoName) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = redisMockExpectDeleteRepoWithCharts(mock, repoName, chartsInCache); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } chartCacheKeys := []string{} @@ -811,14 +813,14 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } responseAfterDelete, err := s.GetAvailablePackageSummaries( context.Background(), &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if len(responseAfterDelete.AvailablePackageSummaries) != 0 { @@ -826,7 +828,7 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -846,25 +848,25 @@ func TestGetAvailablePackageSummaryAfterCacheResync(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, nil, *repo); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } responseBeforeResync, err := s.GetAvailablePackageSummaries( context.Background(), &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareAvailablePackageSummaries(t, responseBeforeResync, valid_index_available_package_summaries) + compareAvailablePackageSummaries(t, responseBeforeResync, valid_index_available_package_summaries_resp) resyncCh, err := s.repoCache.ExpectResync() if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // now lets try to simulate HTTP 410 GONE exception which should force @@ -892,25 +894,25 @@ func TestGetAvailablePackageSummaryAfterCacheResync(t *testing.T) { s.repoCache.WaitUntilResyncComplete() if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = s.redisMockExpectGetFromRepoCache(mock, nil, *repo); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } responseAfterResync, err := s.GetAvailablePackageSummaries( context.Background(), &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareAvailablePackageSummaries(t, responseAfterResync, valid_index_available_package_summaries) + compareAvailablePackageSummaries(t, responseAfterResync, valid_index_available_package_summaries_resp) }) } @@ -1017,7 +1019,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueNotIdle(t *testing.T) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // at this point I'd like to make sure that GetAvailablePackageSummaries returns @@ -1029,7 +1031,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueNotIdle(t *testing.T) resp, err := s.GetAvailablePackageSummaries(context.TODO(), &corev1.GetAvailablePackageSummariesRequest{}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // we need to make sure that response contains packages from all existing repositories @@ -1050,7 +1052,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueNotIdle(t *testing.T) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -1132,7 +1134,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueIdle(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // at this point I'd like to make sure that GetAvailablePackageSummaries returns @@ -1142,7 +1144,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueIdle(t *testing.T) { resp, err := s.GetAvailablePackageSummaries(context.TODO(), &corev1.GetAvailablePackageSummariesRequest{}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // we need to make sure that response contains packages from all existing repositories @@ -1160,7 +1162,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueIdle(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -1738,7 +1740,7 @@ func TestGetOciPackageRepositoryDetail(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -2283,7 +2285,7 @@ func TestGetOciAvailablePackageSummariesWithoutPagination(t *testing.T) { } if err = s.redisMockExpectGetFromRepoCache(mock, tc.request.FilterOptions, repos...); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } response, err := s.GetAvailablePackageSummaries(context.Background(), tc.request) @@ -2297,7 +2299,7 @@ func TestGetOciAvailablePackageSummariesWithoutPagination(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go index a9c951ee0c6..324598668a4 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/test_util_test.go @@ -528,7 +528,7 @@ func compareAvailablePackageVersions(t *testing.T, actual *corev1.GetAvailablePa corev1.GetAvailablePackageVersionsResponse{}, corev1.PackageAppVersion{}) if !cmp.Equal(expected, actual, opts) { - t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts)) + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts)) } } From d5748d454e98433bf2309af820ed152a771f9006 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Wed, 16 Nov 2022 00:54:04 -0800 Subject: [PATCH 05/10] incremental --- .../v1alpha1/release_integration_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go index c14ea52fd9f..feac8fc203d 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go @@ -361,7 +361,7 @@ func TestKindClusterUpdateInstalledPackage(t *testing.T) { t.Logf("Retrying update in [%d] sec due to %s...", waitTime, err.Error()) SleepWithCountdown(t, int(waitTime)) } else { - t.Fatalf("%+v", err) + t.Fatal(err) } } else { break @@ -682,7 +682,7 @@ func TestKindClusterDeleteInstalledPackage(t *testing.T) { } return // done, nothing more to check } else if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } const maxWait = 25 @@ -698,7 +698,7 @@ func TestKindClusterDeleteInstalledPackage(t *testing.T) { if status.Code(err) == codes.NotFound { break // this is the only way to break out of this loop successfully } else { - t.Fatalf("%+v", err) + t.Fatal(err) } } if i == maxWait { @@ -716,7 +716,7 @@ func TestKindClusterDeleteInstalledPackage(t *testing.T) { } exists, err := kubeExistsHelmRelease(t, name) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } else if exists { t.Fatalf("helmrelease [%s] still exists", installedRef) } @@ -727,7 +727,7 @@ func TestKindClusterDeleteInstalledPackage(t *testing.T) { // from cluster (garbage collection) for i := 0; i <= maxWait; i++ { if pods, err := kubeGetPodNames(t, tc.request.TargetContext.Namespace); err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } else if len(pods) == 0 { break } else if len(pods) != 1 { @@ -1528,7 +1528,7 @@ func createAndWaitForHelmRelease( err := kubeAddHelmRepositoryAndCleanup(t, name, tc.repoType, tc.repoUrl, secretName, tc.repoInterval) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } // need to wait until repo is indexed by flux plugin @@ -1579,7 +1579,7 @@ func createAndWaitForHelmRelease( } _, err = kubeCreateAdminServiceAccount(t, svcAcctName) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } // it appears that if service account is deleted before the helmrelease object that uses it, // when you try to delete the helmrelease, the "delete" operation gets stuck and the only @@ -1682,7 +1682,7 @@ func createAndWaitForHelmRelease( tc.expectedPodPrefix, "@TARGET_NS@", tc.request.TargetContext.Namespace) pods, err := kubeGetPodNames(t, tc.request.TargetContext.Namespace) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } if len(pods) != 1 { t.Errorf("expected 1 pod, got: %s", pods) @@ -1727,7 +1727,7 @@ func waitUntilInstallCompletes( grpcContext, &corev1.GetInstalledPackageDetailRequest{InstalledPackageRef: installedPackageRef}) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } if !expectInstallFailure { @@ -1761,7 +1761,7 @@ func waitUntilInstallCompletes( grpcContext, &corev1.GetInstalledPackageResourceRefsRequest{InstalledPackageRef: installedPackageRef}) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } } else { t.Logf("Install of [%s/%s] completed with [%s], userReason: [%s]", From 329ea2576d1dc9b20f50349666855e0eef01663a Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Wed, 16 Nov 2022 19:34:10 -0800 Subject: [PATCH 06/10] incremental --- .../fluxv2/packages/v1alpha1/release_test.go | 24 ++++++++--------- .../v1alpha1/repo_integration_test.go | 26 +++++++++---------- .../fluxv2/packages/v1alpha1/server_test.go | 2 +- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go index 2d790ec86a6..a9c0d9e863f 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_test.go @@ -218,7 +218,7 @@ func TestGetInstalledPackageSummariesWithoutPagination(t *testing.T) { charts, releases, cleanup := newChartsAndReleases(t, tc.existingObjs) s, mock, err := newServerWithChartsAndReleases(t, nil, charts, releases) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } defer cleanup() @@ -226,7 +226,7 @@ func TestGetInstalledPackageSummariesWithoutPagination(t *testing.T) { ts2, repo, err := newHttpRepoAndServeIndex( existing.repoIndex, existing.repoName, existing.repoNamespace, nil, "") if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } defer ts2.Close() @@ -266,7 +266,7 @@ func TestGetInstalledPackageSummariesWithPagination(t *testing.T) { charts, releases, cleanup := newChartsAndReleases(t, existingObjs) s, mock, err := newServerWithChartsAndReleases(t, nil, charts, releases) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } defer cleanup() @@ -274,7 +274,7 @@ func TestGetInstalledPackageSummariesWithPagination(t *testing.T) { ts2, repo, err := newHttpRepoAndServeIndex( existing.repoIndex, existing.repoName, existing.repoNamespace, nil, "") if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } defer ts2.Close() @@ -473,7 +473,7 @@ func TestGetInstalledPackageDetail(t *testing.T) { t, helmReleaseNamespace, []helmReleaseStub{tc.existingHelmStub}) s, mock, err := newServerWithChartsAndReleases(t, actionConfig, charts, repos) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } response, err := s.GetInstalledPackageDetail(context.Background(), tc.request) @@ -636,13 +636,13 @@ func TestCreateInstalledPackage(t *testing.T) { ts, repo, err := newHttpRepoAndServeIndex( tc.existingObjs.repoIndex, tc.existingObjs.repoName, tc.existingObjs.repoNamespace, nil, "") if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } defer ts.Close() s, mock, err := newSimpleServerWithRepos(t, []sourcev1.HelmRepository{*repo}) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } if err = s.redisMockExpectGetFromRepoCache(mock, nil, *repo); err != nil { @@ -856,7 +856,7 @@ func TestUpdateInstalledPackage(t *testing.T) { defer cleanup() s, mock, err := newServerWithChartsAndReleases(t, nil, charts, releases) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } if tc.defaultUpgradePolicyStr != "" { @@ -957,7 +957,7 @@ func TestDeleteInstalledPackage(t *testing.T) { defer cleanup() s, mock, err := newServerWithChartsAndReleases(t, nil, charts, repos) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } response, err := s.DeleteInstalledPackage(context.Background(), tc.request) @@ -1108,7 +1108,7 @@ func TestGetInstalledPackageResourceRefs(t *testing.T) { toHelmReleaseStubs(tc.baseTestCase.ExistingReleases)) server, mock, err := newServerWithChartsAndReleases(t, actionConfig, charts, releases) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } response, err := server.GetInstalledPackageResourceRefs(context.Background(), tc.request) @@ -1142,7 +1142,7 @@ func newChartsAndReleases(t *testing.T, existingK8sObjs []testSpecGetInstalledPa for _, existing := range existingK8sObjs { tarGzBytes, err := os.ReadFile(existing.chartTarGz) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } // stand up an http server just for the duration of this test @@ -1150,7 +1150,7 @@ func newChartsAndReleases(t *testing.T, existingK8sObjs []testSpecGetInstalledPa w.WriteHeader(200) _, err = w.Write(tarGzBytes) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } })) httpServers = append(httpServers, ts) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go index 70a533966f8..47b56bcbbb9 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_integration_test.go @@ -89,7 +89,7 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { Namespace: "default", } if err := kubeCreateSecretAndCleanup(t, newBasicAuthSecret(secretName, "foo", "bar")); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } repoName := types.NamespacedName{ @@ -97,12 +97,12 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { Namespace: "default", } if err := kubeAddHelmRepositoryAndCleanup(t, repoName, "", podinfo_basic_auth_repo_url, secretName.Name, 0); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } // wait until this repo reaches 'Ready' if err := kubeWaitUntilHelmRepositoryIsReady(t, repoName); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } name := types.NamespacedName{ @@ -166,7 +166,7 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { grpcContext, &corev1.GetAvailablePackageDetailRequest{AvailablePackageRef: availablePackageRef}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } compareAvailablePackageDetail( @@ -332,7 +332,7 @@ func TestKindClusterAddPackageRepository(t *testing.T) { if tc.existingSecret != nil { if err := kubeCreateSecretAndCleanup(t, tc.existingSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } @@ -557,7 +557,7 @@ func TestKindClusterGetPackageRepositoryDetail(t *testing.T) { if tc.existingSecret != nil { tc.existingSecret.Namespace = repoNamespace if err := kubeCreateSecretAndCleanup(t, tc.existingSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } secretName = tc.existingSecret.Name } @@ -943,7 +943,7 @@ func TestKindClusterUpdatePackageRepository(t *testing.T) { if tc.oldSecret != nil { tc.oldSecret.Namespace = repoNamespace if err := kubeCreateSecret(t, tc.oldSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } oldSecretName = tc.oldSecret.GetName() if tc.userManagedSecrets { @@ -961,7 +961,7 @@ func TestKindClusterUpdatePackageRepository(t *testing.T) { if tc.newSecret != nil { tc.newSecret.Namespace = repoNamespace if err := kubeCreateSecretAndCleanup(t, tc.newSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } @@ -977,11 +977,11 @@ func TestKindClusterUpdatePackageRepository(t *testing.T) { err := kubeWaitUntilHelmRepositoryIsReady(t, name) if err != nil { if !tc.failed { - t.Fatalf("%v", err) + t.Fatal(err) } else { // sanity check : make sure repo is in failed state if err.Error() != "Failed: failed to fetch Helm repository index: failed to cache index to temporary file: failed to fetch http://fluxv2plugin-testdata-svc.default.svc.cluster.local:80/podinfo-basic-auth/index.yaml : 401 Unauthorized" { - t.Fatalf("%v", err) + t.Fatal(err) } } } @@ -1136,11 +1136,11 @@ func TestKindClusterDeletePackageRepository(t *testing.T) { tc.oldSecret.Namespace = repoNamespace if tc.userManagedSecrets { if err := kubeCreateSecretAndCleanup(t, tc.oldSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } else { if err := kubeCreateSecret(t, tc.oldSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } oldSecretName = tc.oldSecret.GetName() @@ -1148,7 +1148,7 @@ func TestKindClusterDeletePackageRepository(t *testing.T) { if tc.newSecret != nil { tc.newSecret.Namespace = repoNamespace if err := kubeCreateSecretAndCleanup(t, tc.newSecret); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go index 9cca4926943..72f0894f458 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/server_test.go @@ -184,7 +184,7 @@ func TestGetAvailablePackagesStatus(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } From 0a9384b69b3739b0870fd53de319490171231010 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Wed, 16 Nov 2022 23:14:20 -0800 Subject: [PATCH 07/10] incremental --- script/e2e-test.sh | 730 ++++++++++++--------------------------------- 1 file changed, 190 insertions(+), 540 deletions(-) diff --git a/script/e2e-test.sh b/script/e2e-test.sh index b6a6a47b564..08206253247 100755 --- a/script/e2e-test.sh +++ b/script/e2e-test.sh @@ -7,50 +7,22 @@ set -o errexit set -o nounset set -o pipefail -startTime=$(date +%s) - # Constants ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null && pwd)" -ALL_TESTS="all" -MAIN_TESTS="main" -MULTICLUSTER_TESTS="multicluster" -MULTICLUSTER_NOKUBEAPPS_TESTS="multicluster-nokubeapps" -CARVEL_TESTS="carvel" -FLUX_TESTS="flux" -OPERATOR_TESTS="operator" -SUPPORTED_TESTS_GROUPS=("${ALL_TESTS}" "${MAIN_TESTS}" "${MULTICLUSTER_TESTS}" "${CARVEL_TESTS}" "${FLUX_TESTS}" "${OPERATOR_TESTS}" "${MULTICLUSTER_NOKUBEAPPS_TESTS}") -INTEGRATION_HOST=kubeapps-ci.kubeapps -INTEGRATION_ENTRYPOINT="http://${INTEGRATION_HOST}" - -# Params -USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV:-"false"} -OLM_VERSION=${OLM_VERSION:-"v0.18.2"} -IMG_DEV_TAG=${IMG_DEV_TAG:?missing dev tag} -IMG_MODIFIER=${IMG_MODIFIER:-""} -TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES:-"4"} -DEX_IP=${DEX_IP:-"172.18.0.2"} -ADDITIONAL_CLUSTER_IP=${ADDITIONAL_CLUSTER_IP:-"172.18.0.3"} -KAPP_CONTROLLER_VERSION=${KAPP_CONTROLLER_VERSION:-"v0.42.0"} -CHARTMUSEUM_VERSION=${CHARTMUSEUM_VERSION:-"3.9.1"} -# check latest flux releases at https://github.com/fluxcd/flux2/releases -FLUX_VERSION=${FLUX_VERSION:-"v0.36.0"} -GKE_BRANCH=${GKE_BRANCH:-} -IMG_PREFIX=${IMG_PREFIX:-"kubeapps/"} -TESTS_GROUP=${TESTS_GROUP:-"${ALL_TESTS}"} -DEBUG_MODE=${DEBUG_MODE:-false} -TEST_LATEST_RELEASE=${TEST_LATEST_RELEASE:-false} - -# shellcheck disable=SC2076 -if [[ ! " ${SUPPORTED_TESTS_GROUPS[*]} " =~ " ${TESTS_GROUP} " ]]; then - # shellcheck disable=SC2046 - echo $(IFS=','; echo "The provided TEST_GROUP [${TESTS_GROUP}] is not supported. Supported groups are: ${SUPPORTED_TESTS_GROUPS[*]}") - exit 1 -fi +USE_MULTICLUSTER_OIDC_ENV=${1:-false} +OLM_VERSION=${2:-"v0.18.2"} +DEV_TAG=${3:?missing dev tag} +IMG_MODIFIER=${4:-""} +DOCKER_USERNAME=${5:-""} +DOCKER_PASSWORD=${6:-""} +TEST_TIMEOUT_MINUTES=${7:-4} +DEX_IP=${8:-"172.18.0.2"} +ADDITIONAL_CLUSTER_IP=${9:-"172.18.0.3"} # TODO(andresmgot): While we work with beta releases, the Bitnami pipeline # removes the pre-release part of the tag -if [[ -n "${TEST_LATEST_RELEASE}" && "${TEST_LATEST_RELEASE}" != "false" ]]; then - IMG_DEV_TAG=${IMG_DEV_TAG/-beta.*/} +if [[ -n "${TEST_LATEST_RELEASE:-}" ]]; then + DEV_TAG=${DEV_TAG/-beta.*/} fi # Load Generic Libraries @@ -61,91 +33,60 @@ fi # shellcheck disable=SC1090 . "${ROOT_DIR}/script/lib/libutil.sh" -# Get the load balancer IP -if [[ -z "${GKE_BRANCH-}" ]]; then - LOAD_BALANCER_IP=$DEX_IP -else - LOAD_BALANCER_IP=$(kubectl -n nginx-ingress get service nginx-ingress-ingress-nginx-controller -o jsonpath="{.status.loadBalancer.ingress[].ip}") -fi - -# Functions for local Docker registry mgmt -. "${ROOT_DIR}/script/local-docker-registry.sh" - -# Functions for handling Chart Museum -. "${ROOT_DIR}/script/chart-museum.sh" - -info "###############################################################################################" -info "DEBUG_MODE: ${DEBUG_MODE}" -info "TESTS_GROUP: ${TESTS_GROUP}" -info "GKE_BRANCH: ${GKE_BRANCH}" -info "ROOT_DIR: ${ROOT_DIR}" -info "USE_MULTICLUSTER_OIDC_ENV: ${USE_MULTICLUSTER_OIDC_ENV}" -info "OLM_VERSION: ${OLM_VERSION}" -info "CHARTMUSEUM_VERSION: ${CHARTMUSEUM_VERSION}" -info "IMG_DEV_TAG: ${IMG_DEV_TAG}" -info "IMG_MODIFIER: ${IMG_MODIFIER}" -info "IMG_PREFIX: ${IMG_PREFIX}" -info "DEX_IP: ${DEX_IP}" -info "ADDITIONAL_CLUSTER_IP: ${ADDITIONAL_CLUSTER_IP}" -info "LOAD_BALANCER_IP: ${LOAD_BALANCER_IP}" -info "TEST_TIMEOUT_MINUTES: ${TEST_TIMEOUT_MINUTES}" -info "KAPP_CONTROLLER_VERSION: ${KAPP_CONTROLLER_VERSION}" -info "K8S SERVER VERSION: $(kubectl version -o json | jq -r '.serverVersion.gitVersion')" -info "KUBECTL VERSION: $(kubectl version -o json | jq -r '.clientVersion.gitVersion')" -info "###############################################################################################" - -# Auxiliary functions - -# -# Install an authenticated Docker registry inside the cluster -# -setupLocalDockerRegistry() { - info "Installing local Docker registry with authentication" - installLocalRegistry "${ROOT_DIR}" - - info "Pushing test container to local Docker registry" - pushContainerToLocalRegistry -} - -# -# Push a chart that uses container image from the local registry -# -pushLocalChart() { - info "Packaging local test chart" - helm package "${ROOT_DIR}/integration/charts/simplechart" - - info "Pushing local test chart to ChartMuseum" - pushChartToChartMuseum "simplechart" "0.1.0" "simplechart-0.1.0.tgz" +info "Root dir: ${ROOT_DIR}" +info "Use multicluster+OIDC: ${USE_MULTICLUSTER_OIDC_ENV}" +info "OLM version: ${OLM_VERSION}" +info "Image tag: ${DEV_TAG}" +info "Image repo suffix: ${IMG_MODIFIER}" +info "Dex IP: ${DEX_IP}" +info "Additional cluster IP : ${ADDITIONAL_CLUSTER_IP}" +info "Test timeout: ${TEST_TIMEOUT_MINUTES}" +info "Cluster Version: $(kubectl version -o json | jq -r '.serverVersion.gitVersion')" +info "Kubectl Version: $(kubectl version -o json | jq -r '.clientVersion.gitVersion')" +echo "" + +# Auxiliar functions + +######################## +# Test Helm +# Globals: +# HELM_* +# Arguments: None +# Returns: None +######################### +testHelm() { + info "Running Helm tests..." + helm test -n kubeapps kubeapps-ci } -######################################################################################################################## +######################## # Check if the pod that populates de OperatorHub catalog is running # Globals: None # Arguments: None # Returns: None -######################################################################################################################## +######################### isOperatorHubCatalogRunning() { kubectl get pod -n olm -l olm.catalogSource=operatorhubio-catalog -o jsonpath='{.items[0].status.phase}' | grep Running # Wait also for the catalog to be populated kubectl get packagemanifests.packages.operators.coreos.com | grep prometheus } -######################################################################################################################## +######################## # Install OLM # Globals: None # Arguments: # $1: Version of OLM # Returns: None -######################################################################################################################## +######################### installOLM() { local release=$1 info "Installing OLM ${release} ..." - url="https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${release}" + url=https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${release} namespace=olm - kubectl create -f "${url}/crds.yaml" + kubectl apply -f "${url}/crds.yaml" kubectl wait --for=condition=Established -f "${url}/crds.yaml" - kubectl create -f "${url}/olm.yaml" + kubectl apply -f "${url}/olm.yaml" # wait for deployments to be ready kubectl rollout status -w deployment/olm-operator --namespace="${namespace}" @@ -156,7 +97,7 @@ installOLM() { new_csv_phase=$(kubectl get csv -n "${namespace}" packageserver -o jsonpath='{.status.phase}' 2>/dev/null || echo "Waiting for CSV to appear") if [[ $new_csv_phase != "${csv_phase:-}" ]]; then csv_phase=$new_csv_phase - echo "CSV \"packageserver\" phase: ${csv_phase}" + echo "CSV \"packageserver\" phase: $csv_phase" fi if [[ "$new_csv_phase" == "Succeeded" ]]; then break @@ -173,123 +114,72 @@ installOLM() { kubectl rollout status -w deployment/packageserver --namespace="${namespace}" } -######################################################################################################################## +######################## +# Install chartmuseum +# Globals: None +# Arguments: +# $1: Username +# $2: Password +# Returns: None +######################### +installChartmuseum() { + local user=$1 + local password=$2 + info "Installing ChartMuseum ..." + helm install chartmuseum --namespace kubeapps https://github.com/chartmuseum/charts/releases/download/chartmuseum-2.14.2/chartmuseum-2.14.2.tgz \ + --set env.open.DISABLE_API=false \ + --set persistence.enabled=true \ + --set secret.AUTH_USER=$user \ + --set secret.AUTH_PASS=$password + kubectl rollout status -w deployment/chartmuseum-chartmuseum --namespace=kubeapps +} + +######################## # Push a chart to chartmusem # Globals: None # Arguments: # $1: chart # $2: version +# $3: chartmuseum username +# $4: chartmuseum password # Returns: None -######################################################################################################################## +######################### pushChart() { local chart=$1 local version=$2 + local user=$3 + local password=$4 prefix="kubeapps-" description="foo ${chart} chart for CI" info "Adding ${chart}-${version} to ChartMuseum ..." - pullBitnamiChart "${chart}" "${version}" + curl -LO "https://charts.bitnami.com/bitnami/${chart}-${version}.tgz" # Mutate the chart name and description, then re-package the tarball # For instance, the apache's Chart.yaml file becomes modified to: # name: kubeapps-apache # description: foo apache chart for CI # consequently, the new packaged chart is "${prefix}${chart}-${version}.tgz" - # This workaround should mitigate https://github.com/vmware-tanzu/kubeapps/issues/3339 - mkdir "./${chart}-${version}" - tar zxf "${chart}-${version}.tgz" -C "./${chart}-${version}" - # this relies on GNU sed, which is not the default on MacOS - # ref https://gist.github.com/andre3k1/e3a1a7133fded5de5a9ee99c87c6fa0d - sed -i "s/name: ${chart}/name: ${prefix}${chart}/" "./${chart}-${version}/${chart}/Chart.yaml" - sed -i "0,/^\([[:space:]]*description: *\).*/s//\1${description}/" "./${chart}-${version}/${chart}/Chart.yaml" - helm package "./${chart}-${version}/${chart}" -d . - - pushChartToChartMuseum "${chart}" "${version}" "${prefix}${chart}-${version}.tgz" -} - -######################################################################################################################## -# Install kapp-controller -# Globals: None -# Arguments: -# $1: Version of kapp-controller -# Returns: None -######################################################################################################################## -installKappController() { - local release=$1 - info "Installing kapp-controller ${release} ..." - url="https://github.com/vmware-tanzu/carvel-kapp-controller/releases/download/${release}/release.yml" - namespace=kapp-controller - - kubectl apply -f "${url}" - - # wait for deployment to be ready - kubectl rollout status -w deployment/kapp-controller --namespace="${namespace}" - - # Add test repository. - kubectl apply -f https://raw.githubusercontent.com/vmware-tanzu/carvel-kapp-controller/develop/examples/packaging-with-repo/package-repository.yml - - # Add a carvel-reconciler service account to the kubeapps-user-namespace with - # cluster-admin. - kubectl create serviceaccount carvel-reconciler -n kubeapps-user-namespace - kubectl create clusterrolebinding carvel-reconciler --clusterrole=cluster-admin --serviceaccount kubeapps-user-namespace:carvel-reconciler -} - -######################################################################################################################## -# Install flux -# Globals: None -# Arguments: -# $1: Version of flux -# Returns: None -######################################################################################################################## -installFlux() { - local release=$1 - info "Installing flux ${release} ..." - url="https://github.com/fluxcd/flux2/releases/download/${release}/install.yaml" - namespace=flux-system - - kubectl apply -f "${url}" - - # wait for deployment to be ready - k8s_wait_for_deployment "${namespace}" helm-controller - k8s_wait_for_deployment "${namespace}" source-controller - - # Add test repository. - kubectl apply -f https://raw.githubusercontent.com/fluxcd/source-controller/main/config/samples/source_v1beta2_helmrepository.yaml - - # Add a flux-reconciler service account to the kubeapps-user-namespace with - # cluster-admin. - kubectl create serviceaccount flux-reconciler -n kubeapps-user-namespace - kubectl create clusterrolebinding flux-reconciler --clusterrole=cluster-admin --serviceaccount kubeapps-user-namespace:flux-reconciler + # This workaround should mitigate https://github.com/kubeapps/kubeapps/issues/3339 + mkdir ./${chart}-${version} + tar zxf ${chart}-${version}.tgz -C ./${chart}-${version} + sed -i "s/name: ${chart}/name: ${prefix}${chart}/" ./${chart}-${version}/${chart}/Chart.yaml + sed -i "0,/^\([[:space:]]*description: *\).*/s//\1${description}/" ./${chart}-${version}/${chart}/Chart.yaml + helm package ./${chart}-${version}/${chart} -d . + + local POD_NAME=$(kubectl get pods --namespace kubeapps -l "app=chartmuseum" -l "release=chartmuseum" -o jsonpath="{.items[0].metadata.name}") + /bin/sh -c "kubectl port-forward $POD_NAME 8080:8080 --namespace kubeapps &" + sleep 2 + curl -u "${user}:${password}" --data-binary "@${prefix}${chart}-${version}.tgz" http://localhost:8080/api/charts + pkill -f "kubectl port-forward $POD_NAME 8080:8080 --namespace kubeapps" } -######################################################################################################################## -# Creates a Yaml file with additional values for the Helm chart -# Arguments: None -# Returns: Path to the newly created file with additional values -######################################################################################################################## -generateAdditionalValuesFile() { - # Could be done better with $(cat < ${ROOT_DIR}/additional_chart_values.yaml - # But it was breaking the formatting of the file - local valuesFile="${ROOT_DIR}/additional_chart_values.yaml" - echo "ingress: - enabled: true - hostname: localhost - tls: true - selfSigned: true - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-buffer-size: \"8k\" - nginx.ingress.kubernetes.io/proxy-buffers: \"4.0\" - nginx.ingress.kubernetes.io/proxy-read-timeout: \"600.0\"" > "${valuesFile}" - echo "${valuesFile}" -} - -######################################################################################################################## +######################## # Install Kubeapps or upgrades it if it's already installed # Arguments: # $1: chart source # Returns: None -######################################################################################################################## +######################### installOrUpgradeKubeapps() { local chartSource=$1 # Install Kubeapps @@ -299,17 +189,19 @@ installOrUpgradeKubeapps() { # See https://stackoverflow.com/a/36296000 for "${arr[@]+"${arr[@]}"}" notation. cmd=(helm upgrade --install kubeapps-ci --namespace kubeapps "${chartSource}" "${img_flags[@]}" - "${multiclusterFlags[@]+"${multiclusterFlags[@]}"}" "${@:2}" + "${multiclusterFlags[@]+"${multiclusterFlags[@]}"}" --set frontend.replicaCount=1 + --set kubeops.replicaCount=1 --set dashboard.replicaCount=1 --set kubeappsapis.replicaCount=2 + --set kubeops.enabled=true --set postgresql.architecture=standalone --set postgresql.primary.persistence.enabled=false --set postgresql.auth.password=password --set redis.auth.password=password --set apprepository.initialRepos[0].name=bitnami - --set apprepository.initialRepos[0].url=http://chartmuseum.chart-museum.svc.cluster.local:8080 + --set apprepository.initialRepos[0].url=http://chartmuseum-chartmuseum.kubeapps:8080 --set apprepository.initialRepos[0].basicAuth.user=admin --set apprepository.initialRepos[0].basicAuth.password=password --set apprepository.globalReposNamespaceSuffix=-repos-global @@ -319,71 +211,44 @@ installOrUpgradeKubeapps() { "${cmd[@]}" } -######################################################################################################################## -# Formats the provided time in seconds. -# Arguments: -# $1: time in seconds -# Returns: Time formatted as Xm Ys -######################################################################################################################## -formattedElapsedTime() { - local time=$1 - - mins=$((time/60)) - secs=$((time%60)) - echo "${mins}m ${secs}s" -} - -######################################################################################################################## -# Returns the elapsed time since the given starting point. -# Arguments: -# $1: Starting point in seconds (eg. `date +%s`) -# Returns: The elapsed time formatted as Xm Ys -######################################################################################################################## -elapsedTimeSince() { - local start=${1?:Start time not provided} - local end - - end=$(date +%s) - formattedElapsedTime $((end-start)) -} - -[[ "${DEBUG_MODE}" == "true" ]] && set -x; - -if [[ "${DEBUG_MODE}" == "true" && -z ${GKE_BRANCH} ]]; then - info "Docker images loaded in the cluster:" - docker exec kubeapps-ci-control-plane crictl images -fi - # Use dev images or Bitnami if testing the latest release +image_prefix="kubeapps/" kubeapps_apis_image="kubeapps-apis" -[[ -n "${TEST_LATEST_RELEASE}" && "${TEST_LATEST_RELEASE}" != "false" ]] && IMG_PREFIX="bitnami/kubeapps-" && kubeapps_apis_image="apis" +[[ -n "${TEST_LATEST_RELEASE:-}" ]] && image_prefix="bitnami/kubeapps-" && kubeapps_apis_image="apis" images=( "apprepository-controller" "asset-syncer" + "assetsvc" "dashboard" + "kubeops" "pinniped-proxy" "${kubeapps_apis_image}" ) -images=("${images[@]/#/${IMG_PREFIX}}") +images=("${images[@]/#/${image_prefix}}") images=("${images[@]/%/${IMG_MODIFIER}}") img_flags=( - "--set" "apprepository.image.tag=${IMG_DEV_TAG}" + "--set" "apprepository.image.tag=${DEV_TAG}" "--set" "apprepository.image.repository=${images[0]}" - "--set" "apprepository.syncImage.tag=${IMG_DEV_TAG}" + "--set" "apprepository.syncImage.tag=${DEV_TAG}" "--set" "apprepository.syncImage.repository=${images[1]}" - "--set" "dashboard.image.tag=${IMG_DEV_TAG}" - "--set" "dashboard.image.repository=${images[2]}" - "--set" "pinnipedProxy.image.tag=${IMG_DEV_TAG}" - "--set" "pinnipedProxy.image.repository=${images[3]}" - "--set" "kubeappsapis.image.tag=${IMG_DEV_TAG}" - "--set" "kubeappsapis.image.repository=${images[4]}" + "--set" "assetsvc.image.tag=${DEV_TAG}" + "--set" "assetsvc.image.repository=${images[2]}" + "--set" "dashboard.image.tag=${DEV_TAG}" + "--set" "dashboard.image.repository=${images[3]}" + "--set" "kubeops.image.tag=${DEV_TAG}" + "--set" "kubeops.image.repository=${images[4]}" + "--set" "pinnipedProxy.image.tag=${DEV_TAG}" + "--set" "pinnipedProxy.image.repository=${images[5]}" + "--set" "kubeappsapis.image.tag=${DEV_TAG}" + "--set" "kubeappsapis.image.repository=${images[6]}" ) -additional_flags_file=$(generateAdditionalValuesFile) - if [ "$USE_MULTICLUSTER_OIDC_ENV" = true ]; then - basicAuthFlags=( - "--values" "${additional_flags_file}" + multiclusterFlags=( + "--set" "ingress.enabled=true" + "--set" "ingress.hostname=localhost" + "--set" "ingress.tls=true" + "--set" "ingress.selfSigned=true" "--set" "authProxy.enabled=true" "--set" "authProxy.provider=oidc" "--set" "authProxy.clientID=default" @@ -392,20 +257,17 @@ if [ "$USE_MULTICLUSTER_OIDC_ENV" = true ]; then "--set" "authProxy.extraFlags[0]=\"--oidc-issuer-url=https://${DEX_IP}:32000\"" "--set" "authProxy.extraFlags[1]=\"--scope=openid email groups audience:server:client_id:second-cluster audience:server:client_id:third-cluster\"" "--set" "authProxy.extraFlags[2]=\"--ssl-insecure-skip-verify=true\"" - "--set" "authProxy.extraFlags[3]=\"--redirect-url=${INTEGRATION_ENTRYPOINT}/oauth2/callback\"" + "--set" "authProxy.extraFlags[3]=\"--redirect-url=http://kubeapps-ci.kubeapps/oauth2/callback\"" "--set" "authProxy.extraFlags[4]=\"--cookie-secure=false\"" - "--set" "authProxy.extraFlags[5]=\"--cookie-domain=${INTEGRATION_HOST}\"" - "--set" "authProxy.extraFlags[6]=\"--whitelist-domain=${INTEGRATION_HOST}\"" + "--set" "authProxy.extraFlags[5]=\"--cookie-domain=kubeapps-ci.kubeapps\"" + "--set" "authProxy.extraFlags[6]=\"--whitelist-domain=kubeapps-ci.kubeapps\"" "--set" "authProxy.extraFlags[7]=\"--set-authorization-header=true\"" - ) - multiclusterFlags=( "--set" "clusters[0].name=default" "--set" "clusters[1].name=second-cluster" "--set" "clusters[1].apiServiceURL=https://${ADDITIONAL_CLUSTER_IP}:6443" "--set" "clusters[1].insecure=true" - "--set" "clusters[1].serviceToken=$(kubectl --context=kind-kubeapps-ci-additional --kubeconfig="${HOME}/.kube/kind-config-kubeapps-ci-additional" get secret kubeapps-namespace-discovery -o go-template='{{.data.token | base64decode}}')" + "--set" "clusters[1].serviceToken=ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklsbHpiSEp5TlZwM1QwaG9WSE5PYkhVdE5GQkRablY2TW0wd05rUmtMVmxFWVV4MlZEazNaeTEyUmxFaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbXQxWW1WaGNIQnpMVzVoYldWemNHRmpaUzFrYVhOamIzWmxjbmt0ZEc5clpXNHRjV295Ym1naUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNXVZVzFsSWpvaWEzVmlaV0Z3Y0hNdGJtRnRaWE53WVdObExXUnBjMk52ZG1WeWVTSXNJbXQxWW1WeWJtVjBaWE11YVc4dmMyVnlkbWxqWldGalkyOTFiblF2YzJWeWRtbGpaUzFoWTJOdmRXNTBMblZwWkNJNkltVXhaakE1WmpSakxUTTRNemt0TkRJME15MWhZbUptTFRKaU5HWm1OREZrWW1RMllTSXNJbk4xWWlJNkluTjVjM1JsYlRwelpYSjJhV05sWVdOamIzVnVkRHBrWldaaGRXeDBPbXQxWW1WaGNIQnpMVzVoYldWemNHRmpaUzFrYVhOamIzWmxjbmtpZlEuTnh6V2dsUGlrVWpROVQ1NkpWM2xJN1VWTUVSR3J2bklPSHJENkh4dUVwR0luLWFUUzV5Q0pDa3Z0cTF6S3Z3b05sc2MyX0YxaTdFOUxWRGFwbC1UQlhleUN5Rl92S1B1TDF4dTdqZFBMZ1dKT1pQX3JMcXppaDV4ZlkxalFoOHNhdTRZclFJLUtqb3U1UkRRZ0tOQS1BaS1lRlFOZVh2bmlUNlBKYWVkc184V0t3dHRMMC1wdHpYRnBnOFl5dkx6N0U1UWdTR2tjNWpDVXlsS0RvZVRUaVRSOEc2RHFHYkFQQUYwREt0b3MybU9Geno4SlJYNHhoQmdvaUcxVTVmR1g4Z3hnTU1SV0VHRE9kaGMyeXRvcFdRUkRpYmhvaldNS3VDZlNua09zMDRGYTBkYmEwQ0NTbld2a29LZ3Z4QVR5aVVrWm9wV3VpZ1JJNFd5dDkzbXhR" ) - multiclusterFlags+=("${basicAuthFlags[@]+"${basicAuthFlags[@]}"}") fi helm repo add bitnami https://charts.bitnami.com/bitnami @@ -423,27 +285,18 @@ if [[ -n "${TEST_UPGRADE:-}" ]]; then k8s_wait_for_deployment kubeapps kubeapps-ci fi -# Install ChartMuseum -installChartMuseum "${CHARTMUSEUM_VERSION}" -pushChart apache 8.6.2 -pushChart apache 8.6.3 - -# Install Kubeapps installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" info "Waiting for Kubeapps components to be ready (local chart)..." k8s_wait_for_deployment kubeapps kubeapps-ci - -# Setting up local Docker registry if not in GKE -if [[ -z "${GKE_BRANCH-}" ]]; then - setupLocalDockerRegistry - pushLocalChart -fi +installChartmuseum admin password +pushChart apache 8.6.2 admin password +pushChart apache 8.6.3 admin password # Ensure that we are testing the correct image info "" -k8s_ensure_image kubeapps kubeapps-ci-internal-apprepository-controller "$IMG_DEV_TAG" -k8s_ensure_image kubeapps kubeapps-ci-internal-dashboard "$IMG_DEV_TAG" -k8s_ensure_image kubeapps kubeapps-ci-internal-kubeappsapis "$IMG_DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-apprepository-controller "$DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-dashboard "$DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-kubeappsapis "$DEV_TAG" # Wait for Kubeapps Pods info "Waiting for Kubeapps components to be ready..." @@ -481,20 +334,53 @@ for svc in "${svcs[@]}"; do info "Endpoints for ${svc} available" done +# Deactivate helm tests unless we are testing the latest release until +# we have released the code with per-namespace tests (since the helm +# tests for assetsvc needs to test the namespaced repo). +if [[ -z "${TEST_LATEST_RELEASE:-}" ]]; then + # Run helm tests + # Retry once if tests fail to avoid temporary issue + if ! retry_while testHelm "2" "1"; then + warn "PODS status on failure" + kubectl get pods -n kubeapps + for pod in $(kubectl get po -l='app.kubernetes.io/managed-by=Helm,app.kubernetes.io/instance=kubeapps-ci' -oname -n kubeapps); do + warn "LOGS for pod $pod ------------" + if [[ "$pod" =~ .*internal.* ]]; then + kubectl logs -n kubeapps "$pod" + else + kubectl logs -n kubeapps "$pod" nginx + kubectl logs -n kubeapps "$pod" auth-proxy + fi + done + echo + warn "LOGS for dashboard tests --------" + kubectl logs kubeapps-ci-dashboard-test --namespace kubeapps + exit 1 + fi + info "Helm tests succeeded!" +fi + # Browser tests cd "${ROOT_DIR}/integration" -info "Using E2E runner image '${IMG_PREFIX}integration-tests${IMG_MODIFIER}:${IMG_DEV_TAG}'" -kubectl create deployment e2e-runner --image "${IMG_PREFIX}integration-tests${IMG_MODIFIER}:${IMG_DEV_TAG}" -k8s_wait_for_deployment default e2e-runner -pod=$(kubectl get po -l app=e2e-runner -o custom-columns=:metadata.name --no-headers) +kubectl apply -f manifests/executor.yaml +k8s_wait_for_deployment default integration +pod=$(kubectl get po -l run=integration -o jsonpath="{.items[0].metadata.name}") ## Copy config and latest tests for f in *.js; do - kubectl cp "./${f}" "default/${pod}:/app/" + kubectl cp "./${f}" "${pod}:/app/" done -kubectl cp ./tests "default/${pod}:/app/" -info "Copied tests to e2e-runner pod default/${pod}" +# Set tests to be run +# Playwright does not allow to ignore tests on command line, only in config file +testsToRun=("tests/main/") +# Skip the multicluster scenario for GKE +if [[ -z "${GKE_BRANCH-}" ]]; then + testsToRun+=("tests/multicluster/") +fi +testsArgs="$(printf "%s " "${testsToRun[@]}")" +kubectl cp ./tests "${pod}:/app/" +info "Copied tests to integration pod ${pod}" ## Create admin user kubectl create serviceaccount kubeapps-operator -n kubeapps kubectl create clusterrolebinding kubeapps-operator-admin --clusterrole=cluster-admin --serviceaccount kubeapps:kubeapps-operator @@ -509,290 +395,54 @@ kubectl create rolebinding kubeapps-view-user-apprepo-read -n kubeapps-user-name kubectl create rolebinding kubeapps-view-user -n kubeapps-user-namespace --clusterrole=edit --serviceaccount kubeapps:kubeapps-view ## Create edit user kubectl create serviceaccount kubeapps-edit -n kubeapps -# TODO(minelson): Many of these roles/bindings need to be cleaned up. Some are -# unnecessary (with chart changes), some should not be created (such as edit -# here having the edit cluster role in the kubeapps namespace - should just be -# default). See https://github.com/vmware-tanzu/kubeapps/issues/4435 kubectl create rolebinding kubeapps-edit -n kubeapps --clusterrole=edit --serviceaccount kubeapps:kubeapps-edit kubectl create rolebinding kubeapps-edit -n default --clusterrole=edit --serviceaccount kubeapps:kubeapps-edit -kubectl create clusterrolebinding kubeapps-repositories-read --clusterrole kubeapps:kubeapps:apprepositories-read --serviceaccount kubeapps:kubeapps-edit -# TODO(minelson): Similar to the `global-repos-read` rolebinding that the chart -# adds to the `kubeapps-repos-global` namespace for all authenticated users, we -# should eventually consider adding a similar rolebinding for secrets in the -# `kubeapps-repos-global` namespace also (but not if the global repos namespace -# is configured to be the kubeapps namespace, of course.) For now, explicit -# creation because CI tests with a repo with creds in the global repos ns. -# See https://github.com/vmware-tanzu/kubeapps/issues/4435 -kubectl create role view-secrets -n ${GLOBAL_REPOS_NS} --verb=get,list,watch --resource=secrets -kubectl create rolebinding global-repos-secrets-read -n ${GLOBAL_REPOS_NS} --role=view-secrets --serviceaccount kubeapps:kubeapps-edit - -## Give the cluster some time to avoid timeout issues +kubectl create rolebinding kubeapps-repositories-read -n kubeapps --clusterrole kubeapps:kubeapps:apprepositories-read --serviceaccount kubeapps:kubeapps-edit + +## Give the cluster some time to avoid issues like +## https://circleci.com/gh/kubeapps/kubeapps/16102 retry_while "kubectl get -n kubeapps serviceaccount kubeapps-operator -o name" "5" "1" retry_while "kubectl get -n kubeapps serviceaccount kubeapps-view -o name" "5" "1" retry_while "kubectl get -n kubeapps serviceaccount kubeapps-edit -o name" "5" "1" ## Retrieve tokens -admin_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-operator -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" -view_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-view -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" -edit_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-edit -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" - -info "Bootstrap time: $(elapsedTimeSince "$startTime")" - -################################## -######## Main tests group ######## -################################## -if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MAIN_TESTS}" ]]; then - sectionStartTime=$(date +%s) - info "Running main Integration tests without k8s API access..." - test_command=" - CI_TIMEOUT_MINUTES=40 \ - DOCKER_USERNAME=${DOCKER_USERNAME} \ - DOCKER_PASSWORD=${DOCKER_PASSWORD} \ - DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/main/\" - " - info "${test_command}" - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 - fi - info "Main integration tests succeeded!!" - - info "Main tests execution time: $(elapsedTimeSince "$sectionStartTime")" -fi - -########################################### -######## Multi-cluster tests group ######## -########################################### -if [[ -z "${GKE_BRANCH-}" && ("${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MULTICLUSTER_TESTS}") ]]; then - sectionStartTime=$(date +%s) - info "Running multi-cluster integration tests..." - test_command=" - CI_TIMEOUT_MINUTES=40 \ - DOCKER_USERNAME=${DOCKER_USERNAME} \ - DOCKER_PASSWORD=${DOCKER_PASSWORD} \ - DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/multicluster/\" - " - info "${test_command}" - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 - fi - info "Multi-cluster integration tests succeeded!!" - info "Multi-cluster tests execution time: $(elapsedTimeSince "$sectionStartTime")" +admin_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-operator -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" +view_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-view -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" +edit_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-edit -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" + +info "Running main Integration tests without k8s API access..." +if ! kubectl exec -it "$pod" -- /bin/sh -c "CI_TIMEOUT_MINUTES=40 DOCKER_USERNAME=${DOCKER_USERNAME} DOCKER_PASSWORD=${DOCKER_PASSWORD} TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} INTEGRATION_ENTRYPOINT=http://kubeapps-ci.kubeapps USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} ADMIN_TOKEN=${admin_token} VIEW_TOKEN=${view_token} EDIT_TOKEN=${edit_token} yarn test ${testsArgs}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 fi +info "Main integration tests succeeded!!" -#################################### -######## Carvel tests group ######## -#################################### -if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${CARVEL_TESTS}" ]]; then - sectionStartTime=$(date +%s) +## Upgrade and run operator test +# Operators are not supported in GKE 1.14 and flaky in 1.15, skipping test +if [[ -z "${GKE_BRANCH-}" ]] && [[ -n "${TEST_OPERATORS-}" ]]; then + installOLM "${OLM_VERSION}" - ## Upgrade and run Carvel test - installKappController "${KAPP_CONTROLLER_VERSION}" - info "Updating Kubeapps with carvel support" + # Update Kubeapps settings to enable operators and hence proxying + # to k8s API server. + info "Installing latest Kubeapps chart available" installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ - "--set" "packaging.helm.enabled=false" \ - "--set" "packaging.carvel.enabled=true" + "--set" "featureFlags.operators=true" - info "Waiting for updated Kubeapps components to be ready..." + info "Waiting for Kubeapps components to be ready (bitnami chart)..." k8s_wait_for_deployment kubeapps kubeapps-ci - info "Running carvel integration test..." - test_command=" - CI_TIMEOUT_MINUTES=20 \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/carvel/\" - " - info "${test_command}" - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 - fi - info "Carvel integration tests succeeded!!" - info "Carvel tests execution time: $(elapsedTimeSince "$sectionStartTime")" -fi - -#################################### -######## Flux tests group ######## -#################################### -if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${FLUX_TESTS}" ]]; then - sectionStartTime=$(date +%s) - - ## Upgrade and run Flux test - installFlux "${FLUX_VERSION}" - info "Updating Kubeapps with flux support" - installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ - "--set" "packaging.flux.enabled=true" \ - "--set" "packaging.helm.enabled=false" \ - "--set" "packaging.carvel.enabled=false" - - info "Waiting for updated Kubeapps components to be ready..." - k8s_wait_for_deployment kubeapps kubeapps-ci + ## Wait for the Operator catalog to be populated + info "Waiting for the OperatorHub Catalog to be ready ..." + retry_while isOperatorHubCatalogRunning 24 - info "Running flux integration test..." - test_command=" - CI_TIMEOUT_MINUTES=20 \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/flux/\" - " - info "${test_command}" - - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + info "Running operator integration test with k8s API access..." + if ! kubectl exec -it "$pod" -- /bin/sh -c "CI_TIMEOUT_MINUTES=20 TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} INTEGRATION_ENTRYPOINT=http://kubeapps-ci.kubeapps USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} ADMIN_TOKEN=${admin_token} VIEW_TOKEN=${view_token} EDIT_TOKEN=${edit_token} yarn test \"tests/operators/\""; then ## Integration tests failed, get report screenshot warn "PODS status on failure" kubectl cp "${pod}:/app/reports" ./reports exit 1 fi - info "Flux integration tests succeeded!" - info "Flux tests execution time: $(elapsedTimeSince "$sectionStartTime")" -fi - -####################################### -######## Operators tests group ######## -####################################### -if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${OPERATOR_TESTS}" ]]; then - sectionStartTime=$(date +%s) - ## Upgrade and run operator test - # Operators are not supported in GKE 1.14 and flaky in 1.15, skipping test - if [[ -z "${GKE_BRANCH-}" ]] && [[ -n "${TEST_OPERATORS-}" ]]; then - installOLM "${OLM_VERSION}" - - # Update Kubeapps settings to enable operators and hence proxying - # to k8s API server. Don't change the packaging setting to avoid - # re-installing postgres. - info "Installing latest Kubeapps chart available" - installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ - "--set" "packaging.helm.enabled=false" \ - "--set" "packaging.carvel.enabled=true" \ - "--set" "featureFlags.operators=true" - - info "Waiting for Kubeapps components to be ready (bitnami chart)..." - k8s_wait_for_deployment kubeapps kubeapps-ci - - ## Wait for the Operator catalog to be populated - info "Waiting for the OperatorHub Catalog to be ready ..." - retry_while isOperatorHubCatalogRunning 24 - - info "Running operator integration test with k8s API access..." - test_command=" - CI_TIMEOUT_MINUTES=20 \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/operators/\" - " - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 - fi - info "Operator integration tests (with k8s API access) succeeded!!" - info "Operator tests execution time: $(elapsedTimeSince "$sectionStartTime")" - fi + info "Operator integration tests (with k8s API access) succeeded!!" fi - -############################################################ -######## Multi-cluster without Kubeapps tests group ######## -############################################################ -if [[ -z "${GKE_BRANCH-}" && ("${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MULTICLUSTER_NOKUBEAPPS_TESTS}") ]]; then - sectionStartTime=$(date +%s) - info "Running multi-cluster (without Kubeapps cluster) integration tests..." - - info "Updating Kubeapps to exclude Kubeapps cluster from the list of clusters" - - # Update Kubeapps - kubeappsChartPath="${ROOT_DIR}/chart/kubeapps" - info "Installing Kubeapps from ${kubeappsChartPath}..." - kubectl -n kubeapps delete secret localhost-tls || true - - # See https://stackoverflow.com/a/36296000 for "${arr[@]+"${arr[@]}"}" notation. - cmd=(helm upgrade --install kubeapps-ci --namespace kubeapps "${kubeappsChartPath}" - "${img_flags[@]}" - "${basicAuthFlags[@]+"${basicAuthFlags[@]}"}" - --set clusters[0].name=second-cluster - --set clusters[0].apiServiceURL=https://${ADDITIONAL_CLUSTER_IP}:6443 - --set clusters[0].insecure=true - --set clusters[0].serviceToken=$(kubectl --context=kind-kubeapps-ci-additional --kubeconfig=${HOME}/.kube/kind-config-kubeapps-ci-additional get secret kubeapps-namespace-discovery -o go-template='{{.data.token | base64decode}}') - --set frontend.replicaCount=1 - --set dashboard.replicaCount=1 - --set kubeappsapis.replicaCount=2 - --set postgresql.architecture=standalone - --set postgresql.primary.persistence.enabled=false - --set postgresql.auth.password=password - --set redis.auth.password=password - --set apprepository.initialRepos[0].name=bitnami - --set apprepository.initialRepos[0].url=http://chartmuseum.chart-museum.svc.cluster.local:8080 - --set apprepository.initialRepos[0].basicAuth.user=admin - --set apprepository.initialRepos[0].basicAuth.password=password - --set apprepository.globalReposNamespaceSuffix=-repos-global - --set global.postgresql.auth.postgresPassword=password - --wait) - - echo "${cmd[@]}" - "${cmd[@]}" - - info "Waiting for updated Kubeapps components to be ready..." - k8s_wait_for_deployment kubeapps kubeapps-ci - - test_command=" - CI_TIMEOUT_MINUTES=40 \ - DOCKER_USERNAME=${DOCKER_USERNAME} \ - DOCKER_PASSWORD=${DOCKER_PASSWORD} \ - DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ - TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ - INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ - USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ - ADMIN_TOKEN=${admin_token} \ - VIEW_TOKEN=${view_token} \ - EDIT_TOKEN=${edit_token} \ - yarn test \"tests/multicluster-nokubeapps/\" - " - info "${test_command}" - - if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 - fi - info "Multi-cluster integration tests succeeded!!" - - sectionEndTime=$(date +%s) - info "Multi-cluster tests execution time: $(formattedElapsedTime sectionEndTime-sectionStartTime)" -fi - info "Integration tests succeeded!" -info "Total execution time: $(elapsedTimeSince "$startTime")" From 0ec7bee91ff260b6a73ea6641040692d933fb3b6 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Wed, 16 Nov 2022 23:18:44 -0800 Subject: [PATCH 08/10] incremental --- script/e2e-test.sh | 730 +++++++++++++++++++++++++++++++++------------ 1 file changed, 540 insertions(+), 190 deletions(-) diff --git a/script/e2e-test.sh b/script/e2e-test.sh index 08206253247..de25aec8ea6 100755 --- a/script/e2e-test.sh +++ b/script/e2e-test.sh @@ -7,22 +7,50 @@ set -o errexit set -o nounset set -o pipefail +startTime=$(date +%s) + # Constants ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null && pwd)" -USE_MULTICLUSTER_OIDC_ENV=${1:-false} -OLM_VERSION=${2:-"v0.18.2"} -DEV_TAG=${3:?missing dev tag} -IMG_MODIFIER=${4:-""} -DOCKER_USERNAME=${5:-""} -DOCKER_PASSWORD=${6:-""} -TEST_TIMEOUT_MINUTES=${7:-4} -DEX_IP=${8:-"172.18.0.2"} -ADDITIONAL_CLUSTER_IP=${9:-"172.18.0.3"} +ALL_TESTS="all" +MAIN_TESTS="main" +MULTICLUSTER_TESTS="multicluster" +MULTICLUSTER_NOKUBEAPPS_TESTS="multicluster-nokubeapps" +CARVEL_TESTS="carvel" +FLUX_TESTS="flux" +OPERATOR_TESTS="operator" +SUPPORTED_TESTS_GROUPS=("${ALL_TESTS}" "${MAIN_TESTS}" "${MULTICLUSTER_TESTS}" "${CARVEL_TESTS}" "${FLUX_TESTS}" "${OPERATOR_TESTS}" "${MULTICLUSTER_NOKUBEAPPS_TESTS}") +INTEGRATION_HOST=kubeapps-ci.kubeapps +INTEGRATION_ENTRYPOINT="http://${INTEGRATION_HOST}" + +# Params +USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV:-"false"} +OLM_VERSION=${OLM_VERSION:-"v0.18.2"} +IMG_DEV_TAG=${IMG_DEV_TAG:?missing dev tag} +IMG_MODIFIER=${IMG_MODIFIER:-""} +TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES:-"4"} +DEX_IP=${DEX_IP:-"172.18.0.2"} +ADDITIONAL_CLUSTER_IP=${ADDITIONAL_CLUSTER_IP:-"172.18.0.3"} +KAPP_CONTROLLER_VERSION=${KAPP_CONTROLLER_VERSION:-"v0.42.0"} +CHARTMUSEUM_VERSION=${CHARTMUSEUM_VERSION:-"3.9.1"} +# check latest flux releases at https://github.com/fluxcd/flux2/releases +FLUX_VERSION=${FLUX_VERSION:-"v0.36.0"} +GKE_BRANCH=${GKE_BRANCH:-} +IMG_PREFIX=${IMG_PREFIX:-"kubeapps/"} +TESTS_GROUP=${TESTS_GROUP:-"${ALL_TESTS}"} +DEBUG_MODE=${DEBUG_MODE:-false} +TEST_LATEST_RELEASE=${TEST_LATEST_RELEASE:-false} + +# shellcheck disable=SC2076 +if [[ ! " ${SUPPORTED_TESTS_GROUPS[*]} " =~ " ${TESTS_GROUP} " ]]; then + # shellcheck disable=SC2046 + echo $(IFS=','; echo "The provided TEST_GROUP [${TESTS_GROUP}] is not supported. Supported groups are: ${SUPPORTED_TESTS_GROUPS[*]}") + exit 1 +fi # TODO(andresmgot): While we work with beta releases, the Bitnami pipeline # removes the pre-release part of the tag -if [[ -n "${TEST_LATEST_RELEASE:-}" ]]; then - DEV_TAG=${DEV_TAG/-beta.*/} +if [[ -n "${TEST_LATEST_RELEASE}" && "${TEST_LATEST_RELEASE}" != "false" ]]; then + IMG_DEV_TAG=${IMG_DEV_TAG/-beta.*/} fi # Load Generic Libraries @@ -33,60 +61,91 @@ fi # shellcheck disable=SC1090 . "${ROOT_DIR}/script/lib/libutil.sh" -info "Root dir: ${ROOT_DIR}" -info "Use multicluster+OIDC: ${USE_MULTICLUSTER_OIDC_ENV}" -info "OLM version: ${OLM_VERSION}" -info "Image tag: ${DEV_TAG}" -info "Image repo suffix: ${IMG_MODIFIER}" -info "Dex IP: ${DEX_IP}" -info "Additional cluster IP : ${ADDITIONAL_CLUSTER_IP}" -info "Test timeout: ${TEST_TIMEOUT_MINUTES}" -info "Cluster Version: $(kubectl version -o json | jq -r '.serverVersion.gitVersion')" -info "Kubectl Version: $(kubectl version -o json | jq -r '.clientVersion.gitVersion')" -echo "" - -# Auxiliar functions - -######################## -# Test Helm -# Globals: -# HELM_* -# Arguments: None -# Returns: None -######################### -testHelm() { - info "Running Helm tests..." - helm test -n kubeapps kubeapps-ci +# Get the load balancer IP +if [[ -z "${GKE_BRANCH-}" ]]; then + LOAD_BALANCER_IP=$DEX_IP +else + LOAD_BALANCER_IP=$(kubectl -n nginx-ingress get service nginx-ingress-ingress-nginx-controller -o jsonpath="{.status.loadBalancer.ingress[].ip}") +fi + +# Functions for local Docker registry mgmt +. "${ROOT_DIR}/script/local-docker-registry.sh" + +# Functions for handling Chart Museum +. "${ROOT_DIR}/script/chart-museum.sh" + +info "###############################################################################################" +info "DEBUG_MODE: ${DEBUG_MODE}" +info "TESTS_GROUP: ${TESTS_GROUP}" +info "GKE_BRANCH: ${GKE_BRANCH}" +info "ROOT_DIR: ${ROOT_DIR}" +info "USE_MULTICLUSTER_OIDC_ENV: ${USE_MULTICLUSTER_OIDC_ENV}" +info "OLM_VERSION: ${OLM_VERSION}" +info "CHARTMUSEUM_VERSION: ${CHARTMUSEUM_VERSION}" +info "IMG_DEV_TAG: ${IMG_DEV_TAG}" +info "IMG_MODIFIER: ${IMG_MODIFIER}" +info "IMG_PREFIX: ${IMG_PREFIX}" +info "DEX_IP: ${DEX_IP}" +info "ADDITIONAL_CLUSTER_IP: ${ADDITIONAL_CLUSTER_IP}" +info "LOAD_BALANCER_IP: ${LOAD_BALANCER_IP}" +info "TEST_TIMEOUT_MINUTES: ${TEST_TIMEOUT_MINUTES}" +info "KAPP_CONTROLLER_VERSION: ${KAPP_CONTROLLER_VERSION}" +info "K8S SERVER VERSION: $(kubectl version -o json | jq -r '.serverVersion.gitVersion')" +info "KUBECTL VERSION: $(kubectl version -o json | jq -r '.clientVersion.gitVersion')" +info "###############################################################################################" + +# Auxiliary functions + +# +# Install an authenticated Docker registry inside the cluster +# +setupLocalDockerRegistry() { + info "Installing local Docker registry with authentication" + installLocalRegistry "${ROOT_DIR}" + + info "Pushing test container to local Docker registry" + pushContainerToLocalRegistry } -######################## +# +# Push a chart that uses container image from the local registry +# +pushLocalChart() { + info "Packaging local test chart" + helm package "${ROOT_DIR}/integration/charts/simplechart" + + info "Pushing local test chart to ChartMuseum" + pushChartToChartMuseum "simplechart" "0.1.0" "simplechart-0.1.0.tgz" +} + +######################################################################################################################## # Check if the pod that populates de OperatorHub catalog is running # Globals: None # Arguments: None # Returns: None -######################### +######################################################################################################################## isOperatorHubCatalogRunning() { kubectl get pod -n olm -l olm.catalogSource=operatorhubio-catalog -o jsonpath='{.items[0].status.phase}' | grep Running # Wait also for the catalog to be populated kubectl get packagemanifests.packages.operators.coreos.com | grep prometheus } -######################## +######################################################################################################################## # Install OLM # Globals: None # Arguments: # $1: Version of OLM # Returns: None -######################### +######################################################################################################################## installOLM() { local release=$1 info "Installing OLM ${release} ..." - url=https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${release} + url="https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${release}" namespace=olm - kubectl apply -f "${url}/crds.yaml" + kubectl create -f "${url}/crds.yaml" kubectl wait --for=condition=Established -f "${url}/crds.yaml" - kubectl apply -f "${url}/olm.yaml" + kubectl create -f "${url}/olm.yaml" # wait for deployments to be ready kubectl rollout status -w deployment/olm-operator --namespace="${namespace}" @@ -97,7 +156,7 @@ installOLM() { new_csv_phase=$(kubectl get csv -n "${namespace}" packageserver -o jsonpath='{.status.phase}' 2>/dev/null || echo "Waiting for CSV to appear") if [[ $new_csv_phase != "${csv_phase:-}" ]]; then csv_phase=$new_csv_phase - echo "CSV \"packageserver\" phase: $csv_phase" + echo "CSV \"packageserver\" phase: ${csv_phase}" fi if [[ "$new_csv_phase" == "Succeeded" ]]; then break @@ -114,72 +173,123 @@ installOLM() { kubectl rollout status -w deployment/packageserver --namespace="${namespace}" } -######################## -# Install chartmuseum -# Globals: None -# Arguments: -# $1: Username -# $2: Password -# Returns: None -######################### -installChartmuseum() { - local user=$1 - local password=$2 - info "Installing ChartMuseum ..." - helm install chartmuseum --namespace kubeapps https://github.com/chartmuseum/charts/releases/download/chartmuseum-2.14.2/chartmuseum-2.14.2.tgz \ - --set env.open.DISABLE_API=false \ - --set persistence.enabled=true \ - --set secret.AUTH_USER=$user \ - --set secret.AUTH_PASS=$password - kubectl rollout status -w deployment/chartmuseum-chartmuseum --namespace=kubeapps -} - -######################## +######################################################################################################################## # Push a chart to chartmusem # Globals: None # Arguments: # $1: chart # $2: version -# $3: chartmuseum username -# $4: chartmuseum password # Returns: None -######################### +######################################################################################################################## pushChart() { local chart=$1 local version=$2 - local user=$3 - local password=$4 prefix="kubeapps-" description="foo ${chart} chart for CI" info "Adding ${chart}-${version} to ChartMuseum ..." - curl -LO "https://charts.bitnami.com/bitnami/${chart}-${version}.tgz" + pullBitnamiChart "${chart}" "${version}" # Mutate the chart name and description, then re-package the tarball # For instance, the apache's Chart.yaml file becomes modified to: # name: kubeapps-apache # description: foo apache chart for CI # consequently, the new packaged chart is "${prefix}${chart}-${version}.tgz" - # This workaround should mitigate https://github.com/kubeapps/kubeapps/issues/3339 - mkdir ./${chart}-${version} - tar zxf ${chart}-${version}.tgz -C ./${chart}-${version} - sed -i "s/name: ${chart}/name: ${prefix}${chart}/" ./${chart}-${version}/${chart}/Chart.yaml - sed -i "0,/^\([[:space:]]*description: *\).*/s//\1${description}/" ./${chart}-${version}/${chart}/Chart.yaml - helm package ./${chart}-${version}/${chart} -d . - - local POD_NAME=$(kubectl get pods --namespace kubeapps -l "app=chartmuseum" -l "release=chartmuseum" -o jsonpath="{.items[0].metadata.name}") - /bin/sh -c "kubectl port-forward $POD_NAME 8080:8080 --namespace kubeapps &" - sleep 2 - curl -u "${user}:${password}" --data-binary "@${prefix}${chart}-${version}.tgz" http://localhost:8080/api/charts - pkill -f "kubectl port-forward $POD_NAME 8080:8080 --namespace kubeapps" + # This workaround should mitigate https://github.com/vmware-tanzu/kubeapps/issues/3339 + mkdir "./${chart}-${version}" + tar zxf "${chart}-${version}.tgz" -C "./${chart}-${version}" + # this relies on GNU sed, which is not the default on MacOS + # ref https://gist.github.com/andre3k1/e3a1a7133fded5de5a9ee99c87c6fa0d + sed -i "s/name: ${chart}/name: ${prefix}${chart}/" "./${chart}-${version}/${chart}/Chart.yaml" + sed -i "0,/^\([[:space:]]*description: *\).*/s//\1${description}/" "./${chart}-${version}/${chart}/Chart.yaml" + helm package "./${chart}-${version}/${chart}" -d . + + pushChartToChartMuseum "${chart}" "${version}" "${prefix}${chart}-${version}.tgz" } -######################## +######################################################################################################################## +# Install kapp-controller +# Globals: None +# Arguments: +# $1: Version of kapp-controller +# Returns: None +######################################################################################################################## +installKappController() { + local release=$1 + info "Installing kapp-controller ${release} ..." + url="https://github.com/vmware-tanzu/carvel-kapp-controller/releases/download/${release}/release.yml" + namespace=kapp-controller + + kubectl apply -f "${url}" + + # wait for deployment to be ready + kubectl rollout status -w deployment/kapp-controller --namespace="${namespace}" + + # Add test repository. + kubectl apply -f https://raw.githubusercontent.com/vmware-tanzu/carvel-kapp-controller/develop/examples/packaging-with-repo/package-repository.yml + + # Add a carvel-reconciler service account to the kubeapps-user-namespace with + # cluster-admin. + kubectl create serviceaccount carvel-reconciler -n kubeapps-user-namespace + kubectl create clusterrolebinding carvel-reconciler --clusterrole=cluster-admin --serviceaccount kubeapps-user-namespace:carvel-reconciler +} + +######################################################################################################################## +# Install flux +# Globals: None +# Arguments: +# $1: Version of flux +# Returns: None +######################################################################################################################## +installFlux() { + local release=$1 + info "Installing flux ${release} ..." + url="https://github.com/fluxcd/flux2/releases/download/${release}/install.yaml" + namespace=flux-system + + kubectl apply -f "${url}" + + # wait for deployment to be ready + kubectl rollout status -w deployment/helm-controller --namespace="${namespace}" + kubectl rollout status -w deployment/source-controller --namespace="${namespace}" + + # Add test repository. + kubectl apply -f https://raw.githubusercontent.com/fluxcd/source-controller/main/config/samples/source_v1beta2_helmrepository.yaml + + # Add a flux-reconciler service account to the kubeapps-user-namespace with + # cluster-admin. + kubectl create serviceaccount flux-reconciler -n kubeapps-user-namespace + kubectl create clusterrolebinding flux-reconciler --clusterrole=cluster-admin --serviceaccount kubeapps-user-namespace:flux-reconciler +} + +######################################################################################################################## +# Creates a Yaml file with additional values for the Helm chart +# Arguments: None +# Returns: Path to the newly created file with additional values +######################################################################################################################## +generateAdditionalValuesFile() { + # Could be done better with $(cat < ${ROOT_DIR}/additional_chart_values.yaml + # But it was breaking the formatting of the file + local valuesFile="${ROOT_DIR}/additional_chart_values.yaml" + echo "ingress: + enabled: true + hostname: localhost + tls: true + selfSigned: true + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/proxy-buffer-size: \"8k\" + nginx.ingress.kubernetes.io/proxy-buffers: \"4.0\" + nginx.ingress.kubernetes.io/proxy-read-timeout: \"600.0\"" > "${valuesFile}" + echo "${valuesFile}" +} + +######################################################################################################################## # Install Kubeapps or upgrades it if it's already installed # Arguments: # $1: chart source # Returns: None -######################### +######################################################################################################################## installOrUpgradeKubeapps() { local chartSource=$1 # Install Kubeapps @@ -189,19 +299,17 @@ installOrUpgradeKubeapps() { # See https://stackoverflow.com/a/36296000 for "${arr[@]+"${arr[@]}"}" notation. cmd=(helm upgrade --install kubeapps-ci --namespace kubeapps "${chartSource}" "${img_flags[@]}" - "${@:2}" "${multiclusterFlags[@]+"${multiclusterFlags[@]}"}" + "${@:2}" --set frontend.replicaCount=1 - --set kubeops.replicaCount=1 --set dashboard.replicaCount=1 --set kubeappsapis.replicaCount=2 - --set kubeops.enabled=true --set postgresql.architecture=standalone --set postgresql.primary.persistence.enabled=false --set postgresql.auth.password=password --set redis.auth.password=password --set apprepository.initialRepos[0].name=bitnami - --set apprepository.initialRepos[0].url=http://chartmuseum-chartmuseum.kubeapps:8080 + --set apprepository.initialRepos[0].url=http://chartmuseum.chart-museum.svc.cluster.local:8080 --set apprepository.initialRepos[0].basicAuth.user=admin --set apprepository.initialRepos[0].basicAuth.password=password --set apprepository.globalReposNamespaceSuffix=-repos-global @@ -211,44 +319,71 @@ installOrUpgradeKubeapps() { "${cmd[@]}" } +######################################################################################################################## +# Formats the provided time in seconds. +# Arguments: +# $1: time in seconds +# Returns: Time formatted as Xm Ys +######################################################################################################################## +formattedElapsedTime() { + local time=$1 + + mins=$((time/60)) + secs=$((time%60)) + echo "${mins}m ${secs}s" +} + +######################################################################################################################## +# Returns the elapsed time since the given starting point. +# Arguments: +# $1: Starting point in seconds (eg. `date +%s`) +# Returns: The elapsed time formatted as Xm Ys +######################################################################################################################## +elapsedTimeSince() { + local start=${1?:Start time not provided} + local end + + end=$(date +%s) + formattedElapsedTime $((end-start)) +} + +[[ "${DEBUG_MODE}" == "true" ]] && set -x; + +if [[ "${DEBUG_MODE}" == "true" && -z ${GKE_BRANCH} ]]; then + info "Docker images loaded in the cluster:" + docker exec kubeapps-ci-control-plane crictl images +fi + # Use dev images or Bitnami if testing the latest release -image_prefix="kubeapps/" kubeapps_apis_image="kubeapps-apis" -[[ -n "${TEST_LATEST_RELEASE:-}" ]] && image_prefix="bitnami/kubeapps-" && kubeapps_apis_image="apis" +[[ -n "${TEST_LATEST_RELEASE}" && "${TEST_LATEST_RELEASE}" != "false" ]] && IMG_PREFIX="bitnami/kubeapps-" && kubeapps_apis_image="apis" images=( "apprepository-controller" "asset-syncer" - "assetsvc" "dashboard" - "kubeops" "pinniped-proxy" "${kubeapps_apis_image}" ) -images=("${images[@]/#/${image_prefix}}") +images=("${images[@]/#/${IMG_PREFIX}}") images=("${images[@]/%/${IMG_MODIFIER}}") img_flags=( - "--set" "apprepository.image.tag=${DEV_TAG}" + "--set" "apprepository.image.tag=${IMG_DEV_TAG}" "--set" "apprepository.image.repository=${images[0]}" - "--set" "apprepository.syncImage.tag=${DEV_TAG}" + "--set" "apprepository.syncImage.tag=${IMG_DEV_TAG}" "--set" "apprepository.syncImage.repository=${images[1]}" - "--set" "assetsvc.image.tag=${DEV_TAG}" - "--set" "assetsvc.image.repository=${images[2]}" - "--set" "dashboard.image.tag=${DEV_TAG}" - "--set" "dashboard.image.repository=${images[3]}" - "--set" "kubeops.image.tag=${DEV_TAG}" - "--set" "kubeops.image.repository=${images[4]}" - "--set" "pinnipedProxy.image.tag=${DEV_TAG}" - "--set" "pinnipedProxy.image.repository=${images[5]}" - "--set" "kubeappsapis.image.tag=${DEV_TAG}" - "--set" "kubeappsapis.image.repository=${images[6]}" + "--set" "dashboard.image.tag=${IMG_DEV_TAG}" + "--set" "dashboard.image.repository=${images[2]}" + "--set" "pinnipedProxy.image.tag=${IMG_DEV_TAG}" + "--set" "pinnipedProxy.image.repository=${images[3]}" + "--set" "kubeappsapis.image.tag=${IMG_DEV_TAG}" + "--set" "kubeappsapis.image.repository=${images[4]}" ) +additional_flags_file=$(generateAdditionalValuesFile) + if [ "$USE_MULTICLUSTER_OIDC_ENV" = true ]; then - multiclusterFlags=( - "--set" "ingress.enabled=true" - "--set" "ingress.hostname=localhost" - "--set" "ingress.tls=true" - "--set" "ingress.selfSigned=true" + basicAuthFlags=( + "--values" "${additional_flags_file}" "--set" "authProxy.enabled=true" "--set" "authProxy.provider=oidc" "--set" "authProxy.clientID=default" @@ -257,17 +392,20 @@ if [ "$USE_MULTICLUSTER_OIDC_ENV" = true ]; then "--set" "authProxy.extraFlags[0]=\"--oidc-issuer-url=https://${DEX_IP}:32000\"" "--set" "authProxy.extraFlags[1]=\"--scope=openid email groups audience:server:client_id:second-cluster audience:server:client_id:third-cluster\"" "--set" "authProxy.extraFlags[2]=\"--ssl-insecure-skip-verify=true\"" - "--set" "authProxy.extraFlags[3]=\"--redirect-url=http://kubeapps-ci.kubeapps/oauth2/callback\"" + "--set" "authProxy.extraFlags[3]=\"--redirect-url=${INTEGRATION_ENTRYPOINT}/oauth2/callback\"" "--set" "authProxy.extraFlags[4]=\"--cookie-secure=false\"" - "--set" "authProxy.extraFlags[5]=\"--cookie-domain=kubeapps-ci.kubeapps\"" - "--set" "authProxy.extraFlags[6]=\"--whitelist-domain=kubeapps-ci.kubeapps\"" + "--set" "authProxy.extraFlags[5]=\"--cookie-domain=${INTEGRATION_HOST}\"" + "--set" "authProxy.extraFlags[6]=\"--whitelist-domain=${INTEGRATION_HOST}\"" "--set" "authProxy.extraFlags[7]=\"--set-authorization-header=true\"" + ) + multiclusterFlags=( "--set" "clusters[0].name=default" "--set" "clusters[1].name=second-cluster" "--set" "clusters[1].apiServiceURL=https://${ADDITIONAL_CLUSTER_IP}:6443" "--set" "clusters[1].insecure=true" - "--set" "clusters[1].serviceToken=ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklsbHpiSEp5TlZwM1QwaG9WSE5PYkhVdE5GQkRablY2TW0wd05rUmtMVmxFWVV4MlZEazNaeTEyUmxFaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbXQxWW1WaGNIQnpMVzVoYldWemNHRmpaUzFrYVhOamIzWmxjbmt0ZEc5clpXNHRjV295Ym1naUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNXVZVzFsSWpvaWEzVmlaV0Z3Y0hNdGJtRnRaWE53WVdObExXUnBjMk52ZG1WeWVTSXNJbXQxWW1WeWJtVjBaWE11YVc4dmMyVnlkbWxqWldGalkyOTFiblF2YzJWeWRtbGpaUzFoWTJOdmRXNTBMblZwWkNJNkltVXhaakE1WmpSakxUTTRNemt0TkRJME15MWhZbUptTFRKaU5HWm1OREZrWW1RMllTSXNJbk4xWWlJNkluTjVjM1JsYlRwelpYSjJhV05sWVdOamIzVnVkRHBrWldaaGRXeDBPbXQxWW1WaGNIQnpMVzVoYldWemNHRmpaUzFrYVhOamIzWmxjbmtpZlEuTnh6V2dsUGlrVWpROVQ1NkpWM2xJN1VWTUVSR3J2bklPSHJENkh4dUVwR0luLWFUUzV5Q0pDa3Z0cTF6S3Z3b05sc2MyX0YxaTdFOUxWRGFwbC1UQlhleUN5Rl92S1B1TDF4dTdqZFBMZ1dKT1pQX3JMcXppaDV4ZlkxalFoOHNhdTRZclFJLUtqb3U1UkRRZ0tOQS1BaS1lRlFOZVh2bmlUNlBKYWVkc184V0t3dHRMMC1wdHpYRnBnOFl5dkx6N0U1UWdTR2tjNWpDVXlsS0RvZVRUaVRSOEc2RHFHYkFQQUYwREt0b3MybU9Geno4SlJYNHhoQmdvaUcxVTVmR1g4Z3hnTU1SV0VHRE9kaGMyeXRvcFdRUkRpYmhvaldNS3VDZlNua09zMDRGYTBkYmEwQ0NTbld2a29LZ3Z4QVR5aVVrWm9wV3VpZ1JJNFd5dDkzbXhR" + "--set" "clusters[1].serviceToken=$(kubectl --context=kind-kubeapps-ci-additional --kubeconfig="${HOME}/.kube/kind-config-kubeapps-ci-additional" get secret kubeapps-namespace-discovery -o go-template='{{.data.token | base64decode}}')" ) + multiclusterFlags+=("${basicAuthFlags[@]+"${basicAuthFlags[@]}"}") fi helm repo add bitnami https://charts.bitnami.com/bitnami @@ -285,18 +423,27 @@ if [[ -n "${TEST_UPGRADE:-}" ]]; then k8s_wait_for_deployment kubeapps kubeapps-ci fi +# Install ChartMuseum +installChartMuseum "${CHARTMUSEUM_VERSION}" +pushChart apache 8.6.2 +pushChart apache 8.6.3 + +# Install Kubeapps installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" info "Waiting for Kubeapps components to be ready (local chart)..." k8s_wait_for_deployment kubeapps kubeapps-ci -installChartmuseum admin password -pushChart apache 8.6.2 admin password -pushChart apache 8.6.3 admin password + +# Setting up local Docker registry if not in GKE +if [[ -z "${GKE_BRANCH-}" ]]; then + setupLocalDockerRegistry + pushLocalChart +fi # Ensure that we are testing the correct image info "" -k8s_ensure_image kubeapps kubeapps-ci-internal-apprepository-controller "$DEV_TAG" -k8s_ensure_image kubeapps kubeapps-ci-internal-dashboard "$DEV_TAG" -k8s_ensure_image kubeapps kubeapps-ci-internal-kubeappsapis "$DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-apprepository-controller "$IMG_DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-dashboard "$IMG_DEV_TAG" +k8s_ensure_image kubeapps kubeapps-ci-internal-kubeappsapis "$IMG_DEV_TAG" # Wait for Kubeapps Pods info "Waiting for Kubeapps components to be ready..." @@ -334,53 +481,20 @@ for svc in "${svcs[@]}"; do info "Endpoints for ${svc} available" done -# Deactivate helm tests unless we are testing the latest release until -# we have released the code with per-namespace tests (since the helm -# tests for assetsvc needs to test the namespaced repo). -if [[ -z "${TEST_LATEST_RELEASE:-}" ]]; then - # Run helm tests - # Retry once if tests fail to avoid temporary issue - if ! retry_while testHelm "2" "1"; then - warn "PODS status on failure" - kubectl get pods -n kubeapps - for pod in $(kubectl get po -l='app.kubernetes.io/managed-by=Helm,app.kubernetes.io/instance=kubeapps-ci' -oname -n kubeapps); do - warn "LOGS for pod $pod ------------" - if [[ "$pod" =~ .*internal.* ]]; then - kubectl logs -n kubeapps "$pod" - else - kubectl logs -n kubeapps "$pod" nginx - kubectl logs -n kubeapps "$pod" auth-proxy - fi - done - echo - warn "LOGS for dashboard tests --------" - kubectl logs kubeapps-ci-dashboard-test --namespace kubeapps - exit 1 - fi - info "Helm tests succeeded!" -fi - # Browser tests cd "${ROOT_DIR}/integration" -kubectl apply -f manifests/executor.yaml -k8s_wait_for_deployment default integration -pod=$(kubectl get po -l run=integration -o jsonpath="{.items[0].metadata.name}") +info "Using E2E runner image '${IMG_PREFIX}integration-tests${IMG_MODIFIER}:${IMG_DEV_TAG}'" +kubectl create deployment e2e-runner --image "${IMG_PREFIX}integration-tests${IMG_MODIFIER}:${IMG_DEV_TAG}" +k8s_wait_for_deployment default e2e-runner +pod=$(kubectl get po -l app=e2e-runner -o custom-columns=:metadata.name --no-headers) ## Copy config and latest tests for f in *.js; do - kubectl cp "./${f}" "${pod}:/app/" + kubectl cp "./${f}" "default/${pod}:/app/" done -# Set tests to be run -# Playwright does not allow to ignore tests on command line, only in config file -testsToRun=("tests/main/") -# Skip the multicluster scenario for GKE -if [[ -z "${GKE_BRANCH-}" ]]; then - testsToRun+=("tests/multicluster/") -fi -testsArgs="$(printf "%s " "${testsToRun[@]}")" +kubectl cp ./tests "default/${pod}:/app/" +info "Copied tests to e2e-runner pod default/${pod}" -kubectl cp ./tests "${pod}:/app/" -info "Copied tests to integration pod ${pod}" ## Create admin user kubectl create serviceaccount kubeapps-operator -n kubeapps kubectl create clusterrolebinding kubeapps-operator-admin --clusterrole=cluster-admin --serviceaccount kubeapps:kubeapps-operator @@ -395,54 +509,290 @@ kubectl create rolebinding kubeapps-view-user-apprepo-read -n kubeapps-user-name kubectl create rolebinding kubeapps-view-user -n kubeapps-user-namespace --clusterrole=edit --serviceaccount kubeapps:kubeapps-view ## Create edit user kubectl create serviceaccount kubeapps-edit -n kubeapps +# TODO(minelson): Many of these roles/bindings need to be cleaned up. Some are +# unnecessary (with chart changes), some should not be created (such as edit +# here having the edit cluster role in the kubeapps namespace - should just be +# default). See https://github.com/vmware-tanzu/kubeapps/issues/4435 kubectl create rolebinding kubeapps-edit -n kubeapps --clusterrole=edit --serviceaccount kubeapps:kubeapps-edit kubectl create rolebinding kubeapps-edit -n default --clusterrole=edit --serviceaccount kubeapps:kubeapps-edit -kubectl create rolebinding kubeapps-repositories-read -n kubeapps --clusterrole kubeapps:kubeapps:apprepositories-read --serviceaccount kubeapps:kubeapps-edit - -## Give the cluster some time to avoid issues like -## https://circleci.com/gh/kubeapps/kubeapps/16102 +kubectl create clusterrolebinding kubeapps-repositories-read --clusterrole kubeapps:kubeapps:apprepositories-read --serviceaccount kubeapps:kubeapps-edit +# TODO(minelson): Similar to the `global-repos-read` rolebinding that the chart +# adds to the `kubeapps-repos-global` namespace for all authenticated users, we +# should eventually consider adding a similar rolebinding for secrets in the +# `kubeapps-repos-global` namespace also (but not if the global repos namespace +# is configured to be the kubeapps namespace, of course.) For now, explicit +# creation because CI tests with a repo with creds in the global repos ns. +# See https://github.com/vmware-tanzu/kubeapps/issues/4435 +kubectl create role view-secrets -n ${GLOBAL_REPOS_NS} --verb=get,list,watch --resource=secrets +kubectl create rolebinding global-repos-secrets-read -n ${GLOBAL_REPOS_NS} --role=view-secrets --serviceaccount kubeapps:kubeapps-edit + +## Give the cluster some time to avoid timeout issues retry_while "kubectl get -n kubeapps serviceaccount kubeapps-operator -o name" "5" "1" retry_while "kubectl get -n kubeapps serviceaccount kubeapps-view -o name" "5" "1" retry_while "kubectl get -n kubeapps serviceaccount kubeapps-edit -o name" "5" "1" ## Retrieve tokens -admin_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-operator -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" -view_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-view -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" -edit_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-edit -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}' && echo)" - -info "Running main Integration tests without k8s API access..." -if ! kubectl exec -it "$pod" -- /bin/sh -c "CI_TIMEOUT_MINUTES=40 DOCKER_USERNAME=${DOCKER_USERNAME} DOCKER_PASSWORD=${DOCKER_PASSWORD} TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} INTEGRATION_ENTRYPOINT=http://kubeapps-ci.kubeapps USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} ADMIN_TOKEN=${admin_token} VIEW_TOKEN=${view_token} EDIT_TOKEN=${edit_token} yarn test ${testsArgs}"; then - ## Integration tests failed, get report screenshot - warn "PODS status on failure" - kubectl cp "${pod}:/app/reports" ./reports - exit 1 +admin_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-operator -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" +view_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-view -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" +edit_token="$(kubectl get -n kubeapps secret "$(kubectl get -n kubeapps serviceaccount kubeapps-edit -o jsonpath='{.secrets[].name}')" -o go-template='{{.data.token | base64decode}}')" + +info "Bootstrap time: $(elapsedTimeSince "$startTime")" + +################################## +######## Main tests group ######## +################################## +if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MAIN_TESTS}" ]]; then + sectionStartTime=$(date +%s) + info "Running main Integration tests without k8s API access..." + test_command=" + CI_TIMEOUT_MINUTES=40 \ + DOCKER_USERNAME=${DOCKER_USERNAME} \ + DOCKER_PASSWORD=${DOCKER_PASSWORD} \ + DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/main/\" + " + info "${test_command}" + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 + fi + info "Main integration tests succeeded!!" + + info "Main tests execution time: $(elapsedTimeSince "$sectionStartTime")" fi -info "Main integration tests succeeded!!" -## Upgrade and run operator test -# Operators are not supported in GKE 1.14 and flaky in 1.15, skipping test -if [[ -z "${GKE_BRANCH-}" ]] && [[ -n "${TEST_OPERATORS-}" ]]; then - installOLM "${OLM_VERSION}" +########################################### +######## Multi-cluster tests group ######## +########################################### +if [[ -z "${GKE_BRANCH-}" && ("${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MULTICLUSTER_TESTS}") ]]; then + sectionStartTime=$(date +%s) + info "Running multi-cluster integration tests..." + test_command=" + CI_TIMEOUT_MINUTES=40 \ + DOCKER_USERNAME=${DOCKER_USERNAME} \ + DOCKER_PASSWORD=${DOCKER_PASSWORD} \ + DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/multicluster/\" + " + info "${test_command}" + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 + fi + info "Multi-cluster integration tests succeeded!!" + info "Multi-cluster tests execution time: $(elapsedTimeSince "$sectionStartTime")" +fi - # Update Kubeapps settings to enable operators and hence proxying - # to k8s API server. - info "Installing latest Kubeapps chart available" +#################################### +######## Carvel tests group ######## +#################################### +if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${CARVEL_TESTS}" ]]; then + sectionStartTime=$(date +%s) + + ## Upgrade and run Carvel test + installKappController "${KAPP_CONTROLLER_VERSION}" + info "Updating Kubeapps with carvel support" installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ - "--set" "featureFlags.operators=true" + "--set" "packaging.helm.enabled=false" \ + "--set" "packaging.carvel.enabled=true" - info "Waiting for Kubeapps components to be ready (bitnami chart)..." + info "Waiting for updated Kubeapps components to be ready..." k8s_wait_for_deployment kubeapps kubeapps-ci - ## Wait for the Operator catalog to be populated - info "Waiting for the OperatorHub Catalog to be ready ..." - retry_while isOperatorHubCatalogRunning 24 + info "Running carvel integration test..." + test_command=" + CI_TIMEOUT_MINUTES=20 \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/carvel/\" + " + info "${test_command}" + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 + fi + info "Carvel integration tests succeeded!!" + info "Carvel tests execution time: $(elapsedTimeSince "$sectionStartTime")" +fi + +#################################### +######## Flux tests group ######## +#################################### +if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${FLUX_TESTS}" ]]; then + sectionStartTime=$(date +%s) + + ## Upgrade and run Flux test + installFlux "${FLUX_VERSION}" + info "Updating Kubeapps with flux support" + installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ + "--set" "packaging.flux.enabled=true" \ + "--set" "packaging.helm.enabled=false" \ + "--set" "packaging.carvel.enabled=false" + + info "Waiting for updated Kubeapps components to be ready..." + k8s_wait_for_deployment kubeapps kubeapps-ci - info "Running operator integration test with k8s API access..." - if ! kubectl exec -it "$pod" -- /bin/sh -c "CI_TIMEOUT_MINUTES=20 TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} INTEGRATION_ENTRYPOINT=http://kubeapps-ci.kubeapps USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} ADMIN_TOKEN=${admin_token} VIEW_TOKEN=${view_token} EDIT_TOKEN=${edit_token} yarn test \"tests/operators/\""; then + info "Running flux integration test..." + test_command=" + CI_TIMEOUT_MINUTES=20 \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/flux/\" + " + info "${test_command}" + + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then ## Integration tests failed, get report screenshot warn "PODS status on failure" kubectl cp "${pod}:/app/reports" ./reports exit 1 fi - info "Operator integration tests (with k8s API access) succeeded!!" + info "Flux integration tests succeeded!" + info "Flux tests execution time: $(elapsedTimeSince "$sectionStartTime")" +fi + +####################################### +######## Operators tests group ######## +####################################### +if [[ "${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${OPERATOR_TESTS}" ]]; then + sectionStartTime=$(date +%s) + ## Upgrade and run operator test + # Operators are not supported in GKE 1.14 and flaky in 1.15, skipping test + if [[ -z "${GKE_BRANCH-}" ]] && [[ -n "${TEST_OPERATORS-}" ]]; then + installOLM "${OLM_VERSION}" + + # Update Kubeapps settings to enable operators and hence proxying + # to k8s API server. Don't change the packaging setting to avoid + # re-installing postgres. + info "Installing latest Kubeapps chart available" + installOrUpgradeKubeapps "${ROOT_DIR}/chart/kubeapps" \ + "--set" "packaging.helm.enabled=false" \ + "--set" "packaging.carvel.enabled=true" \ + "--set" "featureFlags.operators=true" + + info "Waiting for Kubeapps components to be ready (bitnami chart)..." + k8s_wait_for_deployment kubeapps kubeapps-ci + + ## Wait for the Operator catalog to be populated + info "Waiting for the OperatorHub Catalog to be ready ..." + retry_while isOperatorHubCatalogRunning 24 + + info "Running operator integration test with k8s API access..." + test_command=" + CI_TIMEOUT_MINUTES=20 \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/operators/\" + " + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 + fi + info "Operator integration tests (with k8s API access) succeeded!!" + info "Operator tests execution time: $(elapsedTimeSince "$sectionStartTime")" + fi fi + +############################################################ +######## Multi-cluster without Kubeapps tests group ######## +############################################################ +if [[ -z "${GKE_BRANCH-}" && ("${TESTS_GROUP}" == "${ALL_TESTS}" || "${TESTS_GROUP}" == "${MULTICLUSTER_NOKUBEAPPS_TESTS}") ]]; then + sectionStartTime=$(date +%s) + info "Running multi-cluster (without Kubeapps cluster) integration tests..." + + info "Updating Kubeapps to exclude Kubeapps cluster from the list of clusters" + + # Update Kubeapps + kubeappsChartPath="${ROOT_DIR}/chart/kubeapps" + info "Installing Kubeapps from ${kubeappsChartPath}..." + kubectl -n kubeapps delete secret localhost-tls || true + + # See https://stackoverflow.com/a/36296000 for "${arr[@]+"${arr[@]}"}" notation. + cmd=(helm upgrade --install kubeapps-ci --namespace kubeapps "${kubeappsChartPath}" + "${img_flags[@]}" + "${basicAuthFlags[@]+"${basicAuthFlags[@]}"}" + --set clusters[0].name=second-cluster + --set clusters[0].apiServiceURL=https://${ADDITIONAL_CLUSTER_IP}:6443 + --set clusters[0].insecure=true + --set clusters[0].serviceToken=$(kubectl --context=kind-kubeapps-ci-additional --kubeconfig=${HOME}/.kube/kind-config-kubeapps-ci-additional get secret kubeapps-namespace-discovery -o go-template='{{.data.token | base64decode}}') + --set frontend.replicaCount=1 + --set dashboard.replicaCount=1 + --set kubeappsapis.replicaCount=2 + --set postgresql.architecture=standalone + --set postgresql.primary.persistence.enabled=false + --set postgresql.auth.password=password + --set redis.auth.password=password + --set apprepository.initialRepos[0].name=bitnami + --set apprepository.initialRepos[0].url=http://chartmuseum.chart-museum.svc.cluster.local:8080 + --set apprepository.initialRepos[0].basicAuth.user=admin + --set apprepository.initialRepos[0].basicAuth.password=password + --set apprepository.globalReposNamespaceSuffix=-repos-global + --set global.postgresql.auth.postgresPassword=password + --wait) + + echo "${cmd[@]}" + "${cmd[@]}" + + info "Waiting for updated Kubeapps components to be ready..." + k8s_wait_for_deployment kubeapps kubeapps-ci + + test_command=" + CI_TIMEOUT_MINUTES=40 \ + DOCKER_USERNAME=${DOCKER_USERNAME} \ + DOCKER_PASSWORD=${DOCKER_PASSWORD} \ + DOCKER_REGISTRY_URL=${DOCKER_REGISTRY_URL} \ + TEST_TIMEOUT_MINUTES=${TEST_TIMEOUT_MINUTES} \ + INTEGRATION_ENTRYPOINT=${INTEGRATION_ENTRYPOINT} \ + USE_MULTICLUSTER_OIDC_ENV=${USE_MULTICLUSTER_OIDC_ENV} \ + ADMIN_TOKEN=${admin_token} \ + VIEW_TOKEN=${view_token} \ + EDIT_TOKEN=${edit_token} \ + yarn test \"tests/multicluster-nokubeapps/\" + " + info "${test_command}" + + if ! kubectl exec -it "$pod" -- /bin/sh -c "${test_command}"; then + ## Integration tests failed, get report screenshot + warn "PODS status on failure" + kubectl cp "${pod}:/app/reports" ./reports + exit 1 + fi + info "Multi-cluster integration tests succeeded!!" + + sectionEndTime=$(date +%s) + info "Multi-cluster tests execution time: $(formattedElapsedTime sectionEndTime-sectionStartTime)" +fi + info "Integration tests succeeded!" +info "Total execution time: $(elapsedTimeSince "$startTime")" From 9ae53112155ffd40812f30a7e42a6075b45a1088 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Sun, 20 Nov 2022 18:00:28 -0800 Subject: [PATCH 09/10] Michael's feedback --- .../fluxv2/packages/v1alpha1/cache/chart_cache.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go index eff6c175132..ba26c05b28c 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/cache/chart_cache.go @@ -134,7 +134,11 @@ func (c *ChartCache) SyncCharts(charts []models.Chart, downloadFn DownloadChartF log.Warningf("Skipping chart [%s] due to empty version array", chart.ID) continue } else if len(chart.ChartVersions[0].URLs) == 0 { - log.Warningf("Chart: [%s], version: [%s] has no URLs", chart.ID, chart.ChartVersions[0].Version) + log.Warningf("Skipping chart [%s], version: [%s] has no URLs", chart.ID, chart.ChartVersions[0].Version) + continue + } else if chart.Repo == nil { + // shouldn't happen + log.Warningf("Skipping chart [%s] as it is not associated with any repo", chart.ID) continue } @@ -336,6 +340,9 @@ func (c *ChartCache) DeleteChartsForRepo(repo *types.NamespacedName) error { return c.deleteChartsHelper(repo, sets.String{}) } +// this function is called when re-importing charts after an update to the repo, +// so keepThese is actually populated from the new data, meaning that if the new +// data no longer includes a certain version, it'll get purged here func (c *ChartCache) PurgeObsoleteChartVersions(keepThese []models.Chart) error { log.Infof("+PurgeObsoleteChartVersions()") defer log.Infof("-PurgeObsoleteChartVersions") @@ -343,6 +350,8 @@ func (c *ChartCache) PurgeObsoleteChartVersions(keepThese []models.Chart) error repos := map[types.NamespacedName]sets.String{} for _, ch := range keepThese { if ch.Repo == nil { + // shouldn't happen + log.Warningf("Skipping chart [%s] as it is not associated with any repo", ch.ID) continue } n := types.NamespacedName{ @@ -661,7 +670,7 @@ func ChartCacheComputeValue(chartID, chartUrl, chartVersion string, downloadFn D return nil, err } - log.Infof("Successfully fetched details for chart: [%s], version: [%s], url: [%s], details: [%d] bytes", + log.V(4).Infof("Successfully fetched details for chart: [%s], version: [%s], url: [%s], details: [%d] bytes", chartID, chartVersion, chartUrl, len(chartTgz)) cacheEntryValue := chartCacheEntryValue{ From 285c62152e45b1aed9dbae37c06e1bc5f8f75611 Mon Sep 17 00:00:00 2001 From: gfichtenholt Date: Sun, 20 Nov 2022 18:45:21 -0800 Subject: [PATCH 10/10] fix up a few instances @dlaloue-vmware missed in previous merge in integration tests --- .../packages/v1alpha1/global_vars_test.go | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go index 8f52891d1fa..09e7a004474 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/global_vars_test.go @@ -1373,10 +1373,11 @@ var ( add_repo_req_22 = func(user, password string) *corev1.AddPackageRepositoryRequest { return &corev1.AddPackageRepositoryRequest{ - Name: "my-podinfo-6", - Context: &corev1.Context{Namespace: "default"}, - Type: "oci", - Url: github_stefanprodan_podinfo_oci_registry_url, + Name: "my-podinfo-6", + Context: &corev1.Context{Namespace: "default"}, + Type: "oci", + NamespaceScoped: true, + Url: github_stefanprodan_podinfo_oci_registry_url, Auth: &corev1.PackageRepositoryAuth{ Type: corev1.PackageRepositoryAuth_PACKAGE_REPOSITORY_AUTH_TYPE_BASIC_AUTH, PackageRepoAuthOneOf: &corev1.PackageRepositoryAuth_UsernamePassword{ @@ -1390,10 +1391,11 @@ var ( } add_repo_req_23 = &corev1.AddPackageRepositoryRequest{ - Name: "my-podinfo-7", - Context: &corev1.Context{Namespace: "default"}, - Type: "oci", - Url: github_stefanprodan_podinfo_oci_registry_url, + Name: "my-podinfo-7", + Context: &corev1.Context{Namespace: "default"}, + Type: "oci", + NamespaceScoped: true, + Url: github_stefanprodan_podinfo_oci_registry_url, Auth: &corev1.PackageRepositoryAuth{ Type: corev1.PackageRepositoryAuth_PACKAGE_REPOSITORY_AUTH_TYPE_BASIC_AUTH, PackageRepoAuthOneOf: &corev1.PackageRepositoryAuth_SecretRef{ @@ -1406,10 +1408,11 @@ var ( add_repo_req_24 = func(server, user, password string) *corev1.AddPackageRepositoryRequest { return &corev1.AddPackageRepositoryRequest{ - Name: "my-podinfo-8", - Context: &corev1.Context{Namespace: "default"}, - Type: "oci", - Url: github_stefanprodan_podinfo_oci_registry_url, + Name: "my-podinfo-8", + Context: &corev1.Context{Namespace: "default"}, + Type: "oci", + NamespaceScoped: true, + Url: github_stefanprodan_podinfo_oci_registry_url, Auth: &corev1.PackageRepositoryAuth{ Type: corev1.PackageRepositoryAuth_PACKAGE_REPOSITORY_AUTH_TYPE_DOCKER_CONFIG_JSON, PackageRepoAuthOneOf: &corev1.PackageRepositoryAuth_DockerCreds{