From 8c8e80c3757e5f1a313db675d53cfd4a44f450ef Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Mon, 16 Mar 2026 15:31:53 +0100 Subject: [PATCH 1/2] OCPBUGS-77952: Fix devfile sample import by adding fallback for parent resolution failures --- pkg/devfile/handler.go | 34 ++++++++- pkg/devfile/handler_test.go | 137 ++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 pkg/devfile/handler_test.go diff --git a/pkg/devfile/handler.go b/pkg/devfile/handler.go index e4c394fd40a..544181e15d3 100644 --- a/pkg/devfile/handler.go +++ b/pkg/devfile/handler.go @@ -34,6 +34,35 @@ func DevfileSamplesHandler(w http.ResponseWriter, r *http.Request) { w.Write(sampleIndex) } +// parseDevfileWithFallback attempts to parse the devfile with full parent/plugin +// resolution (flattened). If that fails (e.g. the parent registry is unreachable or +// the OCI pull times out), it retries without flattening. The unflattened parse +// still provides all locally-defined components and commands, which is sufficient +// for the outer-loop resource generation the console backend performs. +func parseDevfileWithFallback(devfileContentBytes []byte, httpTimeout *int) (parser.DevfileObj, error) { + devfileObj, _, err := devfile.ParseDevfileAndValidate(parser.ParserArgs{ + Data: devfileContentBytes, + HTTPTimeout: httpTimeout, + }) + if err == nil { + return devfileObj, nil + } + + klog.Warningf("Flattened devfile parse failed, retrying without parent resolution: %v", err) + + flattenedDevfile := false + devfileObj, _, err = devfile.ParseDevfileAndValidate(parser.ParserArgs{ + Data: devfileContentBytes, + HTTPTimeout: httpTimeout, + FlattenedDevfile: &flattenedDevfile, + }) + if err != nil { + return parser.DevfileObj{}, err + } + + return devfileObj, nil +} + func DevfileHandler(w http.ResponseWriter, r *http.Request) { var ( data DevfileForm @@ -48,11 +77,10 @@ func DevfileHandler(w http.ResponseWriter, r *http.Request) { return } - // Get devfile content and parse it using a library call in the future devfileContentBytes := []byte(data.Devfile.DevfileContent) - //reduce the http request and response timeouts on the devfile library parser to 10s httpTimeout := 10 - devfileObj, _, err = devfile.ParseDevfileAndValidate(parser.ParserArgs{Data: devfileContentBytes, HTTPTimeout: &httpTimeout}) + + devfileObj, err = parseDevfileWithFallback(devfileContentBytes, &httpTimeout) if err != nil { errMsg := "Failed to parse devfile:" if strings.Contains(err.Error(), "schemaVersion not present in devfile") { diff --git a/pkg/devfile/handler_test.go b/pkg/devfile/handler_test.go new file mode 100644 index 00000000000..ca20ca2eea3 --- /dev/null +++ b/pkg/devfile/handler_test.go @@ -0,0 +1,137 @@ +package devfile + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const validDevfileNoParent = ` +schemaVersion: 2.2.0 +metadata: + name: test-app + attributes: + alpha.dockerimage-port: 8080 +components: + - name: image-build + image: + imageName: test-image:latest + dockerfile: + uri: Dockerfile + buildContext: . + - name: kubernetes-deploy + kubernetes: + inlined: | + kind: Deployment + apiVersion: apps/v1 + metadata: + name: my-deploy + spec: + replicas: 1 + selector: + matchLabels: + app: test-app + template: + metadata: + labels: + app: test-app + spec: + containers: + - name: my-container + image: test-image:latest +commands: + - id: build-image + apply: + component: image-build + - id: deployk8s + apply: + component: kubernetes-deploy + - id: deploy + composite: + commands: + - build-image + - deployk8s + group: + kind: deploy + isDefault: true +` + +const devfileWithBadRegistry = ` +schemaVersion: 2.2.0 +metadata: + name: test +parent: + id: nonexistent-stack + registryUrl: 'https://does-not-exist.invalid' +components: + - name: image-build + image: + imageName: test:latest + dockerfile: + uri: Dockerfile + buildContext: . + - name: kubernetes-deploy + kubernetes: + inlined: | + kind: Deployment + apiVersion: apps/v1 + metadata: + name: test-deploy + spec: + replicas: 1 + selector: + matchLabels: + app: test + template: + metadata: + labels: + app: test + spec: + containers: + - name: test + image: test:latest +commands: + - id: build-image + apply: + component: image-build + - id: deployk8s + apply: + component: kubernetes-deploy + - id: deploy + composite: + commands: + - build-image + - deployk8s + group: + kind: deploy + isDefault: true +` + +func TestParseDevfileWithFallback(t *testing.T) { + httpTimeout := 10 + + t.Run("devfile without parent parses on first attempt", func(t *testing.T) { + devfileObj, err := parseDevfileWithFallback([]byte(validDevfileNoParent), &httpTimeout) + assert.NoError(t, err) + + components, err := GetDeployComponents(devfileObj) + assert.NoError(t, err) + assert.Contains(t, components, "image-build") + assert.Contains(t, components, "kubernetes-deploy") + }) + + t.Run("devfile with unreachable parent falls back to unflattened parse", func(t *testing.T) { + devfileObj, err := parseDevfileWithFallback([]byte(devfileWithBadRegistry), &httpTimeout) + assert.NoError(t, err, "fallback to unflattened parse should succeed") + + components, err := GetDeployComponents(devfileObj) + assert.NoError(t, err) + assert.Contains(t, components, "image-build") + assert.Contains(t, components, "kubernetes-deploy") + }) + + t.Run("completely invalid devfile fails both attempts", func(t *testing.T) { + _, err := parseDevfileWithFallback([]byte("not valid yaml: ["), &httpTimeout) + assert.Error(t, err) + }) +} From 319cb18e420be1caa8215818d250a55c73225137 Mon Sep 17 00:00:00 2001 From: Jakub Hadvig Date: Wed, 1 Apr 2026 11:46:21 +0200 Subject: [PATCH 2/2] Enable flaky Import from Git --- .../integration-tests/features/e2e/add-flow-ci.feature | 2 +- .../features/getting-started/sample-card-add-page.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/packages/dev-console/integration-tests/features/e2e/add-flow-ci.feature b/frontend/packages/dev-console/integration-tests/features/e2e/add-flow-ci.feature index 2d65942a6ed..ef7b4d16178 100644 --- a/frontend/packages/dev-console/integration-tests/features/e2e/add-flow-ci.feature +++ b/frontend/packages/dev-console/integration-tests/features/e2e/add-flow-ci.feature @@ -152,7 +152,7 @@ Feature: Create the different workloads from Add page Examples: | card_name | form_header | workload_name | | Httpd | Create Sample application | httpd-sample | - # | Basic Go | Import from Git | go-basic | - OCPBUGS-77952 - flaky test + | Basic Go | Import from Git | go-basic | @regression Scenario: Quick Starts page when no Quick Start has started: QS-03-TC02 diff --git a/frontend/packages/dev-console/integration-tests/features/getting-started/sample-card-add-page.feature b/frontend/packages/dev-console/integration-tests/features/getting-started/sample-card-add-page.feature index 45b5dd459ed..c0623c5e3a2 100644 --- a/frontend/packages/dev-console/integration-tests/features/getting-started/sample-card-add-page.feature +++ b/frontend/packages/dev-console/integration-tests/features/getting-started/sample-card-add-page.feature @@ -27,7 +27,7 @@ Feature: Create Sample Application Examples: | card_name | form_header | workload_name | | Httpd | Create Sample application | httpd-sample | - # | Basic Go | Import from Git | go-basic | - OCPBUGS-77952 - flaky test + | Basic Go | Import from Git | go-basic | @regression Scenario: Review Sample Appliation form: GS-03-TC02