Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions openshift/catalogd/manifests-experimental.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/certified-operator-index:v4.22
ref: registry.redhat.io/redhat/certified-operator-index:v4.23
---
# Source: olmv1/templates/openshift-catalogs/clustercatalog-openshift-community-operators.yml
apiVersion: olm.operatorframework.io/v1
Expand All @@ -1018,7 +1018,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/community-operator-index:v4.22
ref: registry.redhat.io/redhat/community-operator-index:v4.23
---
# Source: olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-operators.yml
apiVersion: olm.operatorframework.io/v1
Expand All @@ -1031,7 +1031,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/redhat-operator-index:v4.22
ref: registry.redhat.io/redhat/redhat-operator-index:v4.23
---
# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml
apiVersion: admissionregistration.k8s.io/v1
Expand Down
6 changes: 3 additions & 3 deletions openshift/catalogd/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/certified-operator-index:v4.22
ref: registry.redhat.io/redhat/certified-operator-index:v4.23
---
# Source: olmv1/templates/openshift-catalogs/clustercatalog-openshift-community-operators.yml
apiVersion: olm.operatorframework.io/v1
Expand All @@ -1017,7 +1017,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/community-operator-index:v4.22
ref: registry.redhat.io/redhat/community-operator-index:v4.23
---
# Source: olmv1/templates/openshift-catalogs/clustercatalog-openshift-redhat-operators.yml
apiVersion: olm.operatorframework.io/v1
Expand All @@ -1030,7 +1030,7 @@ spec:
type: Image
image:
pollIntervalMinutes: 10
ref: registry.redhat.io/redhat/redhat-operator-index:v4.22
ref: registry.redhat.io/redhat/redhat-operator-index:v4.23
---
# Source: olmv1/templates/mutatingwebhookconfiguration-catalogd-mutating-webhook-configuration.yml
apiVersion: admissionregistration.k8s.io/v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var _ = Describe("OLM-Catalog-Validation", func() {

sysCtx := &types.SystemContext{}
if authPath != "" {
fmt.Println("Using registry auth file:", authPath)
fmt.Fprintf(os.Stderr, "Using registry auth file\n")
sysCtx.AuthFilePath = authPath
}

Expand Down
2 changes: 1 addition & 1 deletion openshift/helm/catalogd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ options:
openshift:
enabled: true
catalogs:
version: v4.22
version: v4.23

# The set of namespaces
namespaces:
Expand Down
151 changes: 151 additions & 0 deletions openshift/tests-extension/pkg/helpers/catalog_discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package helpers

import (
"bufio"
"context"
"encoding/json"
"fmt"
"math"
"net/http"
"strings"

//nolint:staticcheck // ST1001: dot-imports for readability
. "github.com/onsi/ginkgo/v2"
//nolint:staticcheck // ST1001: dot-imports for readability
. "github.com/onsi/gomega"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"

olmv1 "github.com/operator-framework/operator-controller/api/v1"

"github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/env"
)

// preferredPackages is tried in order; the first one present in any serving catalog wins.
// Falls back to the first available package if none of these are found.
var preferredPackages = []string{
"quay-operator",
"cluster-logging",
"serverless-operator",
"cli-manager",
"logic-operator",
}

// FindInstallablePackage searches all serving ClusterCatalogs for an installable package,
// favouring preferredPackages in order. Returns the catalog name and package name,
// or fails the test if no packages are found in any serving catalog.
//
// It queries /api/v1/all (always available, no feature gate required) and stops
// streaming each catalog as soon as the highest-priority package is found.
func FindInstallablePackage(ctx context.Context) (string, string) {
cfg := env.Get().RestCfg
httpClient, err := rest.HTTPClientFor(cfg)
Expect(err).ToNot(HaveOccurred(), "failed to build HTTP client from REST config")

k8sClient := env.Get().K8sClient
catalogList := &olmv1.ClusterCatalogList{}
Expect(k8sClient.List(ctx, catalogList)).To(Succeed(), "failed to list ClusterCatalogs")

// Build a rank map: package name → index in preferredPackages (lower = higher priority).
wantedRank := make(map[string]int, len(preferredPackages))
for i, p := range preferredPackages {
wantedRank[p] = i
}

bestCatalog, bestPkg, bestRank := "", "", math.MaxInt

for i := range catalogList.Items {
cc := &catalogList.Items[i]
if !meta.IsStatusConditionPresentAndEqual(cc.Status.Conditions, olmv1.TypeServing, metav1.ConditionTrue) {
fmt.Fprintf(GinkgoWriter, "Catalog %q is not serving, skipping\n", cc.Name)
continue
}

pkg, rank, qErr := findPackageInCatalog(ctx, httpClient, cfg.Host, cc.Name, wantedRank)
if qErr != nil {
fmt.Fprintf(GinkgoWriter, "Warning: could not query catalog %q: %v\n", cc.Name, qErr)
continue
}
if pkg == "" {
fmt.Fprintf(GinkgoWriter, "Catalog %q: no packages found\n", cc.Name)
continue
}

fmt.Fprintf(GinkgoWriter, "Catalog %q: found %q (rank %d)\n", cc.Name, pkg, rank)
if rank < bestRank {
bestCatalog, bestPkg, bestRank = cc.Name, pkg, rank
}
}

if bestPkg != "" {
fmt.Fprintf(GinkgoWriter, "Selected package %q from catalog %q\n", bestPkg, bestCatalog)
return bestCatalog, bestPkg
}

Fail("no installable packages found in any serving catalog")
return "", "" // unreachable
}

// findPackageInCatalog streams the catalog's /api/v1/all endpoint (which is always
// available, unlike /api/v1/metas which requires the NewOLMCatalogdAPIV1Metas feature
// gate), reads the full response, filters for olm.package objects, and returns the
// highest-ranked preferred package found across the whole catalog.
//
// rank == math.MaxInt means the returned package is a fallback (not in wantedRank).
// Returns ("", 0, nil) when no packages are found.
func findPackageInCatalog(ctx context.Context, httpClient *http.Client, apiServerHost, catalogName string, wantedRank map[string]int) (string, int, error) {
url := strings.TrimRight(apiServerHost, "/") +
fmt.Sprintf("/api/v1/namespaces/openshift-catalogd/services/https:catalogd-service:443/proxy/catalogs/%s/api/v1/all",
catalogName)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", 0, fmt.Errorf("build request: %w", err)
}
resp, err := httpClient.Do(req)
if err != nil {
return "", 0, fmt.Errorf("GET: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", 0, fmt.Errorf("unexpected status %d from %s", resp.StatusCode, url)
}

// Response is JSONL: one JSON object per line. Only olm.package lines carry
// a "name" field we care about; bundle/channel lines are much larger and skipped.
var (
fallbackPkg string
bestPkg string
bestRank = math.MaxInt
)

scanner := bufio.NewScanner(resp.Body)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
var obj struct {
Schema string `json:"schema"`
Name string `json:"name"`
}
if err := json.Unmarshal(scanner.Bytes(), &obj); err != nil || obj.Schema != "olm.package" || obj.Name == "" {
continue
}
if fallbackPkg == "" {
fallbackPkg = obj.Name
}
if rank, ok := wantedRank[obj.Name]; ok && rank < bestRank {
bestRank = rank
bestPkg = obj.Name
}
}
if sErr := scanner.Err(); sErr != nil {
return "", 0, sErr
}

if bestPkg != "" {
return bestPkg, bestRank, nil
}
return fallbackPkg, math.MaxInt, nil
}
13 changes: 8 additions & 5 deletions openshift/tests-extension/test/olmv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,17 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1
})

It("should install an openshift catalog cluster extension", Label("original-name:[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 operator installation should install an openshift catalog cluster extension"), func(ctx SpecContext) {
By("ensuring no ClusterExtension and CRD for quay-operator")
helpers.EnsureCleanupClusterExtension(context.Background(), "quay-operator", "quayregistries.quay.redhat.com")
catalog, pkg := helpers.FindInstallablePackage(ctx)

By("applying the ClusterExtension resource")
name, cleanup := helpers.CreateClusterExtension("quay-operator", "3.13.10", namespace, "")
By(fmt.Sprintf("ensuring no existing ClusterExtension for %q", pkg))
helpers.EnsureCleanupClusterExtension(context.Background(), pkg, "")

By(fmt.Sprintf("applying ClusterExtension for %q from catalog %q", pkg, catalog))
name, cleanup := helpers.CreateClusterExtension(pkg, "", namespace, "",
helpers.WithCatalogNameSelector(catalog))
DeferCleanup(cleanup)

By("waiting for the quay-operator ClusterExtension to be installed")
By(fmt.Sprintf("waiting for %q to be installed", pkg))
helpers.ExpectClusterExtensionToBeInstalled(ctx, name)
})
})
Expand Down