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..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 } @@ -259,10 +263,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 +290,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 +312,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 +333,52 @@ 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{}) +} + +// 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") + + 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{ + 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() @@ -620,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{ 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..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,134 @@ 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)) + }) + } +} + +// 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) + } + } + compareAvailablePackageDetail( + 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) + } + compareAvailablePackageDetail( + 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) + } + compareAvailablePackageDetail( + 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..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,10 +16,7 @@ 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" - 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" @@ -233,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) @@ -328,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 { @@ -575,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) @@ -685,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) }) } } @@ -779,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) }) } } @@ -860,6 +842,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 +907,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 @@ -1043,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) } @@ -1141,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) @@ -1274,22 +1255,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 7276112fcea..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 @@ -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{ @@ -836,6 +876,10 @@ var ( }, } + 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", @@ -889,70 +933,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", + expected_summaries_before_update = &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", + expected_summaries_after_update = &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{ @@ -1324,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{ @@ -1341,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{ @@ -1357,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{ @@ -2688,7 +2740,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, }, } @@ -2735,7 +2787,7 @@ var ( }, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2755,7 +2807,7 @@ var ( CertAuthority: redactedString, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2813,7 +2865,7 @@ var ( }, }, }, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2827,7 +2879,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: tls_auth_redacted, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2845,7 +2897,7 @@ var ( Url: "https://example.repo.com/charts", Interval: "1m", Auth: secret_1_auth, - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, }, } @@ -2859,7 +2911,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, }, } @@ -2873,7 +2925,7 @@ var ( Url: podinfo_repo_url, Interval: "10m", Auth: &corev1.PackageRepositoryAuth{PassCredentials: false}, - Status: podinfo_repo_status_3, + Status: podinfo_repo_status_1, }, } @@ -3200,7 +3252,7 @@ var ( NamespaceScoped: true, Type: "helm", Url: "http://example.com", - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, RequiresAuth: true, } @@ -3211,7 +3263,7 @@ var ( NamespaceScoped: true, Type: "helm", Url: "http://example.com", - Status: podinfo_repo_status_2, + Status: podinfo_repo_status_1, RequiresAuth: false, } @@ -3249,7 +3301,7 @@ var ( NamespaceScoped: true, Type: "helm", Url: podinfo_repo_url, - Status: podinfo_repo_status_3, + Status: podinfo_repo_status_1, RequiresAuth: false, } } @@ -3725,21 +3777,14 @@ var ( } podinfo_repo_status_1 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '9d3ac1eb708dfaebae14d7c88fd46afce8b1e0f7aace790d91758575dc8ce518'", - } - - podinfo_repo_status_2 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '651f952130ea96823711d08345b85e82be011dc6'", - } - - podinfo_repo_status_3 = &corev1.PackageRepositoryStatus{ - Ready: true, - Reason: corev1.PackageRepositoryStatus_STATUS_REASON_SUCCESS, - UserReason: "Succeeded: stored artifact for revision '2867920fb8f56575f4bc95ed878ee2a0c8ae79cdd2bca210a72aa3ff04defa1b'", + 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_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_integration_test.go b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/release_integration_test.go index 50ebcc60a61..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 @@ -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, }) @@ -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 { @@ -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) } } @@ -1542,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 @@ -1593,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 @@ -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: @@ -1696,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) @@ -1741,7 +1727,7 @@ func waitUntilInstallCompletes( grpcContext, &corev1.GetInstalledPackageDetailRequest{InstalledPackageRef: installedPackageRef}) if err != nil { - t.Fatalf("%+v", err) + t.Fatal(err) } if !expectInstallFailure { @@ -1775,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]", 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..a9c0d9e863f 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" @@ -219,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() @@ -227,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() @@ -246,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) @@ -280,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() @@ -288,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() @@ -384,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) @@ -488,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) @@ -506,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 { @@ -651,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 { @@ -871,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 != "" { @@ -972,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) @@ -1123,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) @@ -1157,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 @@ -1165,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) @@ -1232,31 +1217,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 264a4f55d6d..181401f3a7f 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo.go @@ -846,7 +846,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..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{ @@ -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) @@ -176,10 +166,10 @@ func TestKindClusterRepoWithBasicAuth(t *testing.T) { grpcContext, &corev1.GetAvailablePackageDetailRequest{AvailablePackageRef: availablePackageRef}) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } - compareActualVsExpectedAvailablePackageDetail( + compareAvailablePackageDetail( t, resp.AvailablePackageDetail, expected_detail_podinfo_basic_auth(repoName.Name).AvailablePackageDetail) @@ -342,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) } } @@ -567,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 } @@ -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,18 +806,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)) - } + comparePackageRepositorySummaries(t, resp, tc.expectedResponse) }) } } @@ -964,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 { @@ -982,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) } } @@ -998,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) } } } @@ -1069,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) }) } } @@ -1157,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() @@ -1169,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) } } @@ -1408,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) { @@ -1476,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) @@ -1513,40 +1487,10 @@ 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) }) } -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 893831267c4..8270b155c46 100644 --- a/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go +++ b/cmd/kubeapps-apis/plugins/fluxv2/packages/v1alpha1/repo_test.go @@ -59,10 +59,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_resp, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request namespace is specified)", @@ -74,10 +72,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_resp, }, { name: "it returns a couple of fluxv2 packages from the cluster (when request cluster is specified and matches the kubeapps cluster)", @@ -93,9 +89,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_resp, }, { name: "it returns all fluxv2 packages from the cluster (when request namespace is does not match repo namespace)", @@ -440,13 +434,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) } } @@ -461,14 +455,9 @@ 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)) + t.Fatal(err) } + compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) } } @@ -505,7 +494,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{ @@ -567,15 +556,13 @@ 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 { 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"}, @@ -589,18 +576,16 @@ 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 { 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) + t.Fatal(err) } }) } @@ -658,7 +643,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() @@ -666,30 +651,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) } - 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, 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) @@ -720,7 +694,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)) @@ -729,15 +703,12 @@ func TestGetAvailablePackageSummaryAfterRepoIndexUpdate(t *testing.T) { ctx, &corev1.GetAvailablePackageSummariesRequest{Context: &corev1.Context{}}) 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)) + t.Fatal(err) } + compareAvailablePackageSummaries(t, responsePackagesAfterUpdate, expected_summaries_after_update) if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } } }) @@ -789,32 +760,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) } - 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_resp) // now we are going to simulate the user deleting a HelmRepository CR which, in turn, // causes k8s server to fire a DELETE event @@ -825,11 +785,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{} @@ -855,14 +815,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 { @@ -870,7 +830,7 @@ func TestGetAvailablePackageSummaryAfterFluxHelmRepoDelete(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -890,35 +850,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) } - 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_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 @@ -946,27 +896,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) } - 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_resp) }) } @@ -1073,7 +1021,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 @@ -1085,7 +1033,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 @@ -1106,7 +1054,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueNotIdle(t *testing.T) } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -1188,7 +1136,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 @@ -1198,7 +1146,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 @@ -1216,7 +1164,7 @@ func TestGetAvailablePackageSummariesAfterCacheResyncQueueIdle(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -1730,22 +1678,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)) - } + comparePackageRepositoryDetail(t, actualResp, tc.expectedResponse) } } }) @@ -1804,28 +1737,12 @@ 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) + t.Fatal(err) } }) } @@ -1910,18 +1827,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)) - } + comparePackageRepositorySummaries(t, response, tc.expectedResponse) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) @@ -2176,22 +2082,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 { @@ -2396,7 +2287,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) @@ -2410,14 +2301,9 @@ 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)) + t.Fatal(err) } + compareAvailablePackageSummaries(t, response, tc.expectedResponse) }) } } @@ -2504,14 +2390,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 f5d36e0db24..512f62956eb 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" @@ -183,7 +184,7 @@ func TestGetAvailablePackagesStatus(t *testing.T) { } if err = mock.ExpectationsWereMet(); err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } }) } @@ -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 2121b980b6d..1c8da0f75a4 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 @@ -14,11 +14,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" @@ -118,11 +121,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 { @@ -399,6 +397,166 @@ func testCert(name string) string { return "./testdata/cert/" + name } +func compareAvailablePackageDetail(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 !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) + } + 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 comparePackageRepositorySummaries(t *testing.T, actual *corev1.GetPackageRepositorySummariesResponse, expected *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(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(expected.PackageRepositorySummaries)) + copy(copyE, expected.PackageRepositorySummaries) + sort.Slice(copyE, func(i, j int) bool { return copyE[i].Name < copyE[j].Name }) + + 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 + 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 comparePackageRepositoryDetail(t *testing.T, actual *corev1.GetPackageRepositoryDetailResponse, expected *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 !cmp.Equal(expected, actual, opts1, opts2) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts1, opts2)) + } + + if !strings.HasPrefix(actual.GetDetail().Status.UserReason, expected.Detail.Status.UserReason) { + t.Fatalf("unexpected response (status.UserReason): (-want +got):\n- %s\n+ %s", + expected.Detail.Status.UserReason, + actual.GetDetail().Status.UserReason) + } +} + +func compareInstalledPackageDetail(t *testing.T, actual *corev1.GetInstalledPackageDetailResponse, expected *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 !cmp.Equal(expected, actual, opts, opts2, opts3) { + t.Fatalf("mismatch (-want +got):\n%s", cmp.Diff(expected, actual, opts, opts2, opts3)) + } + 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.Fatalf("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)) + } +} + // misc global vars that get re-used in multiple tests var ( fluxPlugin = &plugins.Plugin{Name: "fluxv2.packages", Version: "v1alpha1"}