-
Notifications
You must be signed in to change notification settings - Fork 82
🐛 Fix flaky generate-demos CI job #2838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import ( | |
| "context" | ||
| "crypto/tls" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
|
|
@@ -41,6 +42,10 @@ func bash(ctx context.Context, script string) (string, error) { | |
|
|
||
| if err != nil { | ||
| logger.V(1).Info("Failed to run", "command", script, "stderr", stderr, "error", err) | ||
| var exitErr *exec.ExitError | ||
| if errors.As(err, &exitErr) { | ||
| exitErr.Stderr = stderrBuf.Bytes() | ||
| } | ||
| } | ||
| logger.V(1).Info("Output", "command", script, "output", stdout) | ||
|
|
||
|
|
@@ -81,7 +86,15 @@ func CatalogReportsConditionWithoutReason(ctx context.Context, catalogUserName, | |
| func ensureCatalogPortForward(ctx context.Context) (string, error) { | ||
| sc := scenarioCtx(ctx) | ||
| if sc.catalogAddr != "" { | ||
| return sc.catalogAddr, nil | ||
| if catalogPortForwardAlive(sc.catalogAddr) { | ||
| return sc.catalogAddr, nil | ||
| } | ||
| logger.V(1).Info("Catalog port-forward is dead, re-establishing", "addr", sc.catalogAddr) | ||
| if sc.catalogCleanup != nil { | ||
| sc.catalogCleanup() | ||
| } | ||
| sc.catalogAddr = "" | ||
| sc.catalogCleanup = nil | ||
| } | ||
|
|
||
| addr, cleanup, err := portForward(ctx, componentNamespaces["catalogd"], "service/catalogd-service", 443) | ||
|
|
@@ -92,68 +105,87 @@ func ensureCatalogPortForward(ctx context.Context) (string, error) { | |
| sc.catalogCleanup = cleanup | ||
|
|
||
| waitFor(ctx, func() bool { | ||
| client := &http.Client{ | ||
| Timeout: 3 * time.Second, | ||
| Transport: &http.Transport{ | ||
| TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec | ||
| DialContext: (&net.Dialer{Timeout: 2 * time.Second}).DialContext, | ||
| }, | ||
| } | ||
| resp, err := client.Get(fmt.Sprintf("https://%s/", addr)) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| resp.Body.Close() | ||
| return true | ||
| return catalogPortForwardAlive(addr) | ||
| }) | ||
| return addr, nil | ||
| } | ||
|
|
||
| func catalogPortForwardAlive(addr string) bool { | ||
| client := &http.Client{ | ||
| Timeout: 3 * time.Second, | ||
| Transport: &http.Transport{ | ||
| TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec | ||
| DialContext: (&net.Dialer{Timeout: 2 * time.Second}).DialContext, | ||
| }, | ||
| } | ||
| resp, err := client.Get(fmt.Sprintf("https://%s/", addr)) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| resp.Body.Close() | ||
| return true | ||
| } | ||
|
|
||
| func catalogCurlJq(ctx context.Context, catalogName, jqFilter string) (string, error) { | ||
| addr, err := ensureCatalogPortForward(ctx) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| script := fmt.Sprintf( | ||
| `curl -s -k https://%s/catalogs/%s/api/v1/all | jq -s '%s'`, | ||
| `curl -sS -k --retry 3 --retry-delay 2 --retry-all-errors https://%s/catalogs/%s/api/v1/all | jq '%s'`, | ||
| addr, catalogName, jqFilter, | ||
| ) | ||
|
Comment on lines
134
to
137
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch — updated to |
||
| return bash(ctx, script) | ||
| } | ||
|
|
||
| func CatalogContainsSomePackages(ctx context.Context, catalogName string) error { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| `.[] | select(.schema == "olm.package") | .name`) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| return fmt.Errorf("catalog %q contains no packages", catalogName) | ||
| } | ||
| waitFor(ctx, func() bool { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| `objects | select(.schema == "olm.package") | .name`) | ||
| if err != nil { | ||
| logger.Info("Catalog query failed, retrying", "catalog", catalogName, "error", err, "stderr", stderrOutput(err)) | ||
| return false | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| logger.Info("Catalog returned no packages, retrying", "catalog", catalogName) | ||
| return false | ||
| } | ||
| return true | ||
| }) | ||
|
Comment on lines
+142
to
+154
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These changed functions will no longer return an error. Should the context be checked for a timeout so that an error can be returned?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| return nil | ||
| } | ||
|
|
||
| func PackageHasSomeChannels(ctx context.Context, packageName, catalogName string) error { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| fmt.Sprintf(`.[] | select(.schema == "olm.channel") | select(.package == "%s") | .name`, packageName)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| return fmt.Errorf("package %q in catalog %q has no channels", packageName, catalogName) | ||
| } | ||
| waitFor(ctx, func() bool { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| fmt.Sprintf(`objects | select(.schema == "olm.channel") | select(.package == "%s") | .name`, packageName)) | ||
| if err != nil { | ||
| logger.Info("Catalog query failed, retrying", "catalog", catalogName, "package", packageName, "error", err, "stderr", stderrOutput(err)) | ||
| return false | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| logger.Info("Package has no channels, retrying", "catalog", catalogName, "package", packageName) | ||
| return false | ||
| } | ||
| return true | ||
| }) | ||
| return nil | ||
| } | ||
|
|
||
| func PackageHasSomeBundles(ctx context.Context, packageName, catalogName string) error { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| fmt.Sprintf(`.[] | select(.schema == "olm.bundle") | select(.package == "%s") | .name`, packageName)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| return fmt.Errorf("package %q in catalog %q has no bundles", packageName, catalogName) | ||
| } | ||
| waitFor(ctx, func() bool { | ||
| out, err := catalogCurlJq(ctx, catalogName, | ||
| fmt.Sprintf(`objects | select(.schema == "olm.bundle") | select(.package == "%s") | .name`, packageName)) | ||
| if err != nil { | ||
| logger.Info("Catalog query failed, retrying", "catalog", catalogName, "package", packageName, "error", err, "stderr", stderrOutput(err)) | ||
| return false | ||
| } | ||
| if strings.TrimSpace(out) == "" { | ||
| logger.Info("Package has no bundles, retrying", "catalog", catalogName, "package", packageName) | ||
| return false | ||
| } | ||
| return true | ||
| }) | ||
| return nil | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need
jq -sbecause the catalog format is in JSONLines (jsonl) format, not monolithic JSON, and slurp-mode forces it to re-represent it inside a monolithic object. Without this, jq will error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jq handles JSON Lines (JSONL/NDJSON) natively — each JSON object on a separate line is read as a separate input and the filter is applied to each one independently. No
-sneeded:The filters were adjusted to remove the
.[] |prefix that was only needed in slurp mode (where jq wraps all inputs into a single array). Without-s, jq iterates over inputs automatically, soselect(...)applies directly to each object.The benefit of dropping
-sis constant-memory processing — jq processes each JSON object as it arrives from the pipe instead of buffering the entire catalog (~100+ MB for operatorhubio) into memory before processing. The originalexit status 5(jq system error) was likely caused by this memory pressure. The full test suite (all 4 scenarios, 30 steps) passes locally with this change.