Skip to content
Closed
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
248 changes: 248 additions & 0 deletions .github/workflows/test-bundle-helpers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
name: Test Bundle Helpers

on:
push:
tags:
- '*'
branches:
- master
- release-*
pull_request:
types:
- opened
- reopened
- synchronize

env:
ROX_PRODUCT_BRANDING: RHACS_BRANDING
RELATED_IMAGE_MAIN: foo
RELATED_IMAGE_SCANNER: foo
RELATED_IMAGE_SCANNER_SLIM: foo
RELATED_IMAGE_SCANNER_DB: foo
RELATED_IMAGE_SCANNER_DB_SLIM: foo
RELATED_IMAGE_COLLECTOR: foo
RELATED_IMAGE_ROXCTL: foo
RELATED_IMAGE_CENTRAL_DB: foo
RELATED_IMAGE_SCANNER_V4_DB: foo
RELATED_IMAGE_SCANNER_V4: foo

jobs:
test-python-implementation:
strategy:
matrix:
related_img_mode:
- konflux
- omit
- downstream
name: Test Python Implementation
runs-on: ubuntu-latest
defaults:
run:
working-directory: operator

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r bundle_helpers/requirements.txt

- name: Run Python unit tests
run: make test-bundle-helpers

- name: Generate bundle with Python
env:
USE_GO_BUNDLE_HELPER: false
RELATED_IMAGES_MODE: ${{ matrix.related_img_mode }}
run: |
set -euo pipefail
go mod tidy
make bundle bundle-post-process
mkdir -p /tmp/artifacts
cp -r bundle /tmp/artifacts/bundle-python
if [ -d build/bundle ]; then
cp -r build/bundle /tmp/artifacts/build-bundle-python
fi
git diff || true

- name: Upload Python bundle artifacts
uses: actions/upload-artifact@v4
with:
name: bundle-python-${{ matrix.related_img_mode }}
path: /tmp/artifacts/
retention-days: 1

test-go-implementation:
strategy:
fail-fast: false
matrix:
related_img_mode:
- konflux
- omit
- downstream
name: Test Go Implementation
runs-on: ubuntu-latest
defaults:
run:
working-directory: operator

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Run Go unit tests
run: |
cd bundle_helpers
go test ./...

# For python fallback
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

# For python fallback
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r bundle_helpers/requirements.txt

- name: Generate bundle with Go
env:
USE_GO_BUNDLE_HELPER: true
RELATED_IMAGES_MODE: ${{ matrix.related_img_mode }}
run: |
set -euo pipefail
go mod tidy
make bundle bundle-post-process
mkdir -p /tmp/artifacts
cp -r bundle /tmp/artifacts/bundle-go
if [ -d build/bundle ]; then
cp -r build/bundle /tmp/artifacts/build-bundle-go
fi
git diff || true

- name: Upload Go bundle artifacts
uses: actions/upload-artifact@v4
with:
name: bundle-go-${{ matrix.related_img_mode }}
path: /tmp/artifacts/
retention-days: 1

compare-implementations:
strategy:
fail-fast: false
matrix:
related_img_mode:
- konflux
- omit
- downstream
name: Compare Python and Go Outputs
runs-on: ubuntu-latest
needs: [test-python-implementation, test-go-implementation]
# Only run comparison if Go implementation exists
if: always() && needs.test-go-implementation.result == 'success'

steps:
- name: Download Python bundle
uses: actions/download-artifact@v4
with:
name: bundle-python-${{ matrix.related_img_mode }}
path: /tmp/python

- name: Download Go bundle
uses: actions/download-artifact@v4
with:
name: bundle-go-${{ matrix.related_img_mode }}
path: /tmp/go
continue-on-error: true

- name: Install comparison tools
run: |
sudo apt-get update
sudo apt-get install -y diffutils

- name: Compare bundle outputs
run: |
echo "=== Comparing Python and Go bundle outputs ==="

if diff -ruN /tmp/python/bundle-python /tmp/go/bundle-go; then
echo "✓ SUCCESS: Bundle outputs are identical"
else
echo "✗ FAILURE: Bundle outputs differ"
exit 1
fi
echo ""
echo "=== Pruning createdAt lines..."
sed -i '/^ createdAt:/d' /tmp/*/build-bundle-*/manifests/rhacs-operator.clusterserviceversion.yaml

echo ""
echo "=== Comparing build/bundle outputs ==="
if diff -ruN /tmp/python/build-bundle-python /tmp/go/build-bundle-go; then
echo "✓ SUCCESS: Build bundle outputs are identical"
else
echo "✗ FAILURE: Build bundle outputs differ"
exit 1
fi
echo ""
echo "=== Listing contents"
find /tmp/python /tmp/go -ls
find /tmp/python /tmp/go -type f -print0 | xargs -0 md5sum | sort

status-check:
strategy:
matrix:
related_img_mode:
- konflux
- omit
- downstream
name: Bundle Helper Tests Status
runs-on: ubuntu-latest
needs: [test-python-implementation, test-go-implementation, compare-implementations]
if: always()

steps:
- name: Check test results
run: |
python_result="${{ needs.test-python-implementation.result }}"
go_result="${{ needs.test-go-implementation.result }}"
compare_result="${{ needs.compare-implementations.result }}"

echo "Python tests: $python_result"
echo "Go tests: $go_result"
echo "Comparison: $compare_result"

if [ "$python_result" != "success" ]; then
echo "✗ Python tests failed"
exit 1
fi

if [ "$go_result" != "success" ]; then
echo "✗ Go tests failed"
exit 1
fi

if [ "$compare_result" != "success" ]; then
echo "✗ Go/Python comparison failed"
exit 1
fi

echo "✓ All required tests passed"
10 changes: 6 additions & 4 deletions operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -462,22 +462,24 @@ bundle: yq manifests kustomize operator-sdk ## Generate bundle manifests and met
# Yet we want most of the contents autogenerated from the Makefile variables as a single source of truth.
# Therefore we append ".extra" file to the end of bundle's dockerfile.
cat bundle.Dockerfile.extra >> bundle.Dockerfile
# Run a python script to fix the orders in the specDescriptors (children must not appear before their parents).
# Fix the orders in the specDescriptors (children must not appear before their parents).
set -euo pipefail ;\
$(ACTIVATE_PYTHON) ;\
bundle_helpers/fix-spec-descriptor-order.py \
./bundle_helpers/dispatch.sh fix-spec-descriptor-order \
<bundle/manifests/rhacs-operator.clusterserviceversion.yaml \
>bundle/manifests/rhacs-operator.clusterserviceversion.yaml.fixed
mv bundle/manifests/rhacs-operator.clusterserviceversion.yaml.fixed \
bundle/manifests/rhacs-operator.clusterserviceversion.yaml
$(OPERATOR_SDK) bundle validate ./bundle --select-optional suite=operatorframework

RELATED_IMAGES_MODE ?= omit

.PHONY: bundle-post-process
bundle-post-process: test-bundle-helpers operator-sdk ## Post-process CSV file to include correct operator versions, etc.
set -euo pipefail ;\
$(ACTIVATE_PYTHON) ;\
first_version=3.62.0 `# 3.62.0 is the first operator version ever released` ;\
candidate_version=$$(./bundle_helpers/patch-csv.py \
candidate_version=$$(./bundle_helpers/dispatch.sh patch-csv \
--use-version $(VERSION) \
--first-version $${first_version} \
--operator-image $(IMG) \
Expand All @@ -495,7 +497,7 @@ bundle-post-process: test-bundle-helpers operator-sdk ## Post-process CSV file t
--use-version=$(VERSION) \
--first-version=$${first_version} \
--operator-image=$(IMG) \
--related-images-mode=omit \
--related-images-mode=$(RELATED_IMAGES_MODE) \
$${unreleased_opt:-}
# Check that the resulting bundle still passes validations.
$(OPERATOR_SDK) bundle validate ./build/bundle --select-optional suite=operatorframework
Expand Down
77 changes: 77 additions & 0 deletions operator/bundle_helpers/cmd/fix_descriptors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cmd

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"

"github.com/stackrox/rox/operator/bundle_helpers/pkg/descriptor"
"gopkg.in/yaml.v3"
)

// FixSpecDescriptorOrder fixes the ordering of specDescriptors in a CSV file.
// It reads from stdin and writes to stdout, matching the Python script behavior.
func FixSpecDescriptorOrder(args []string) error {
if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") {
fmt.Println("Usage: bundle-helper fix-spec-descriptor-order < input.yaml > output.yaml")
fmt.Println()
fmt.Println("Fixes the ordering of specDescriptors in a ClusterServiceVersion YAML file.")
fmt.Println("Ensures parent descriptors appear before their children.")
return nil
}

// Read CSV from stdin
data, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("failed to read stdin: %w", err)
}

// Parse YAML into a map (like Python's yaml.safe_load)
var csvDoc map[string]interface{}
if err := yaml.Unmarshal(data, &csvDoc); err != nil {
return fmt.Errorf("failed to parse YAML: %w", err)
}

// Process descriptors
if err := descriptor.FixCSVDescriptorsMap(csvDoc); err != nil {
return fmt.Errorf("failed to fix descriptors: %w", err)
}

// Encode to YAML using Go's yaml.v3
var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
if err := encoder.Encode(csvDoc); err != nil {
return fmt.Errorf("failed to encode YAML: %w", err)
}
if err := encoder.Close(); err != nil {
return fmt.Errorf("failed to close encoder: %w", err)
}

// Normalize through Python to match PyYAML's exact formatting
// This is the "escape hatch" mentioned in the migration plan
return normalizeYAMLOutput(buf.Bytes(), os.Stdout)
}

// normalizeYAMLOutput pipes YAML through the Python normalizer to match PyYAML formatting.
// This handles formatting quirks (quote styles, line wrapping, etc.) while keeping
// all business logic in Go.
func normalizeYAMLOutput(goYAML []byte, w io.Writer) error {
wd, _ := os.Getwd()
normalizerPath := filepath.Join(wd, "bundle_helpers", "yaml-normalizer.py")

// Run the normalizer
cmd := exec.Command(normalizerPath)
cmd.Stdin = bytes.NewReader(goYAML)
cmd.Stdout = w
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to normalize YAML: %w", err)
}

return nil
}
7 changes: 7 additions & 0 deletions operator/bundle_helpers/cmd/patch_csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cmd

// PatchCSV patches a ClusterServiceVersion YAML file with version and image information.
// This is a placeholder implementation - to be implemented in future phases.
func PatchCSV(args []string) error {
panic("not yet implemented")
}
Loading
Loading