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
24 changes: 24 additions & 0 deletions .tekton/file-integrity-operator-bundle-dev-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ spec:
name: CHAINS-GIT_COMMIT
value: $(tasks.clone-repository.results.commit)
tasks:
- name: wait-for-operator-image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vincent056 I'm starting to think that we need to have a separate pipeline that builds the Bundle image from the PR changes.

I say this because ideally we should be building the images only when relevant changes to each image is made:

  • We only build the operator image when changes for the operator code are made
  • We should only build the bundle image when relevant changes for the bundle are made.

There will be cases when we may only need or want to build the bundle image. In this case there is no operator image build to wait for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think bundle should always be built as long as we have triggered operator build in a PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is why we need to have this changed in the operator image pipeliene.

The bundle pipeline may not always have an operator image to wait for if only the bundle image was triggered.
A bundle build should not require an operator build to happen as well.

params:
- name: OPERATOR_IMAGE
value: quay.io/redhat-user-workloads/ocp-isc-tenant/file-integrity-operator-dev:on-pr-$(params.revision)
- name: ARCHITECTURES
value: "linux/amd64,linux/ppc64le,linux/s390x"
- name: MAX_WAIT_TIME
value: "30m"
- name: CHECK_INTERVAL
value: "30s"
taskRef:
resolver: git
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.revision)
- name: pathInRepo
value: ".tekton/tasks/wait-for-operator-image-task.yaml"
- name: init
params:
- name: image-url
Expand All @@ -145,6 +164,8 @@ spec:
value: $(params.skip-checks)
- name: enable-cache-proxy
value: $(params.enable-cache-proxy)
runAfter:
- wait-for-operator-image
taskRef:
params:
- name: name
Expand Down Expand Up @@ -230,6 +251,7 @@ spec:
- name: BUILD_ARGS
value:
- $(params.build-args[*])
- OPERATOR_IMAGE=$(tasks.wait-for-operator-image.results.OPERATOR_IMAGE_WITH_DIGEST)
- name: BUILD_ARGS_FILE
value: $(params.build-args-file)
- name: PRIVILEGED_NESTED
Expand All @@ -246,6 +268,7 @@ spec:
value: $(tasks.init.results.no-proxy)
runAfter:
- prefetch-dependencies
- wait-for-operator-image
taskRef:
params:
- name: name
Expand Down Expand Up @@ -458,6 +481,7 @@ spec:
- name: BUILD_ARGS
value:
- $(params.build-args[*])
- OPERATOR_IMAGE=$(tasks.wait-for-operator-image.results.OPERATOR_IMAGE_WITH_DIGEST)
- name: BUILD_ARGS_FILE
value: $(params.build-args-file)
- name: SOURCE_ARTIFACT
Expand Down
127 changes: 127 additions & 0 deletions .tekton/tasks/wait-for-operator-image-task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: wait-for-operator-image
spec:
description: |
Waits for an operator image to be available in the registry with all required architectures.
Returns the full image reference with digest (registry/repo/image@sha256:digest).
params:
- name: OPERATOR_IMAGE
description: The operator image to wait for (with tag, e.g., quay.io/.../image:tag)
type: string
- name: ARCHITECTURES
description: Comma-separated list of architectures to wait for (e.g., "linux/amd64,linux/ppc64le,linux/s390x")
type: string
default: "linux/amd64,linux/ppc64le,linux/s390x"
- name: MAX_WAIT_TIME
description: Maximum time to wait (e.g., 30m, 1h)
type: string
default: "30m"
- name: CHECK_INTERVAL
description: Interval between checks (e.g., 30s, 1m)
type: string
default: "30s"
results:
- name: OPERATOR_IMAGE_WITH_DIGEST
description: Full operator image reference with digest (registry/repo/image@sha256:digest)
steps:
- name: wait
image: quay.io/openshift/origin-cli:latest
script: |
#!/bin/bash
set -e

OPERATOR_IMAGE=$(params.OPERATOR_IMAGE)
ARCHITECTURES_PARAM=$(params.ARCHITECTURES)
MAX_WAIT_TIME=$(params.MAX_WAIT_TIME)
CHECK_INTERVAL=$(params.CHECK_INTERVAL)

echo "Waiting for operator image to be available: ${OPERATOR_IMAGE}"
echo "Required architectures: ${ARCHITECTURES_PARAM}"
echo "Max wait time: ${MAX_WAIT_TIME}, Check interval: ${CHECK_INTERVAL}"

# Convert comma-separated architectures to array
IFS=',' read -ra ARCHITECTURES <<< "$ARCHITECTURES_PARAM"

# Convert MAX_WAIT_TIME to seconds
MAX_SECONDS=$(echo "$MAX_WAIT_TIME" | awk '{
if ($0 ~ /[0-9]+s/) { print int($0) }
else if ($0 ~ /[0-9]+m/) { print int($0) * 60 }
else if ($0 ~ /[0-9]+h/) { print int($0) * 3600 }
else { print int($0) }
}')

# Convert CHECK_INTERVAL to seconds
INTERVAL_SECONDS=$(echo "$CHECK_INTERVAL" | awk '{
if ($0 ~ /[0-9]+s/) { print int($0) }
else if ($0 ~ /[0-9]+m/) { print int($0) * 60 }
else if ($0 ~ /[0-9]+h/) { print int($0) * 3600 }
else { print int($0) }
}')

START_TIME=$(date +%s)
ELAPSED=0

while [ $ELAPSED -lt $MAX_SECONDS ]; do
# Check if all required architectures are available
ALL_ARCH_AVAILABLE=true
MISSING_ARCHS=()

for ARCH in "${ARCHITECTURES[@]}"; do
if ! oc image info "$OPERATOR_IMAGE" --filter-by-os="$ARCH" &>/dev/null; then
ALL_ARCH_AVAILABLE=false
MISSING_ARCHS+=("$ARCH")
fi
done

if [ "$ALL_ARCH_AVAILABLE" = true ]; then
echo "✓ Operator image is now available with all architectures: ${OPERATOR_IMAGE}"
echo "Available architectures:"
for ARCH in "${ARCHITECTURES[@]}"; do
echo " - ${ARCH}"
done

# Get the manifest list digest (this is what we need for the bundle)
# The manifest list digest is shown when we query without --filter-by-os
MANIFEST_DIGEST=$(oc image info "$OPERATOR_IMAGE" 2>&1 | grep -i "manifest list:" | awk '{print $3}' || echo "")
if [ -z "$MANIFEST_DIGEST" ]; then
# Try alternative method - get digest from image info
MANIFEST_DIGEST=$(oc image info "$OPERATOR_IMAGE" --filter-by-os=linux/amd64 2>&1 | grep -i "manifest list:" | awk '{print $3}' || echo "")
fi

if [ -z "$MANIFEST_DIGEST" ] || [[ ! "$MANIFEST_DIGEST" =~ ^sha256: ]]; then
echo "Error: Could not get manifest list digest from image: ${OPERATOR_IMAGE}"
exit 1
fi

# Extract the base image name (remove tag and any existing digest)
# OPERATOR_IMAGE format: registry/repo/image:tag
# We want: registry/repo/image@sha256:digest
BASE_IMAGE="${OPERATOR_IMAGE%%:*}" # Remove everything after first :
if [ "$BASE_IMAGE" = "$OPERATOR_IMAGE" ]; then
# No tag found, try removing @digest if present
BASE_IMAGE="${OPERATOR_IMAGE%%@*}"
fi

OPERATOR_IMAGE_WITH_DIGEST="${BASE_IMAGE}@${MANIFEST_DIGEST}"
echo "Manifest list digest: ${MANIFEST_DIGEST}"
echo "Operator image with digest: ${OPERATOR_IMAGE_WITH_DIGEST}"
echo "${OPERATOR_IMAGE_WITH_DIGEST}" > $(results.OPERATOR_IMAGE_WITH_DIGEST.path)

exit 0
else
echo "Waiting for image architectures... (elapsed: ${ELAPSED}s / ${MAX_SECONDS}s)"
echo "Missing architectures: ${MISSING_ARCHS[*]}"
fi
sleep $INTERVAL_SECONDS

CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
done

echo "✗ Timeout: Operator image not available after ${MAX_WAIT_TIME}"
echo "Image: ${OPERATOR_IMAGE}"
echo "This may indicate the operator pipeline failed or is still running."
exit 1

52 changes: 34 additions & 18 deletions bundle-hack/update_csv.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package main

import (
"encoding/base64"
"fmt"
"gopkg.in/yaml.v3"
"log"
"os"
"path/filepath"
"strings"
"encoding/base64"
"fmt"
"log"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"
)

func readCSV(csvFilename string, csv *map[string]interface{}) {
Expand Down Expand Up @@ -51,7 +52,7 @@ func getInputCSVFilePath(dir string) string {

for _, filename := range filenames {
if strings.HasSuffix(filename.Name(), "clusterserviceversion.yaml") {
return filepath.Join(dir,filename.Name())
return filepath.Join(dir, filename.Name())
}
}

Expand All @@ -63,15 +64,15 @@ func getOutputCSVFilePath(dir string, version string) string {
return filepath.Join(dir, fmt.Sprintf("file-integrity-operator.v%s.clusterserviceversion.yaml", version))
}

func addRequiredAnnotations(csv map[string]interface{}){
func addRequiredAnnotations(csv map[string]interface{}) {
requiredAnnotations := map[string]string{
"features.operators.openshift.io/disconnected": "true",
"features.operators.openshift.io/fips-compliant": "true",
"features.operators.openshift.io/proxy-aware": "false",
"features.operators.openshift.io/tls-profiles": "false",
"features.operators.openshift.io/token-auth-aws": "false",
"features.operators.openshift.io/disconnected": "true",
"features.operators.openshift.io/fips-compliant": "true",
"features.operators.openshift.io/proxy-aware": "false",
"features.operators.openshift.io/tls-profiles": "false",
"features.operators.openshift.io/token-auth-aws": "false",
"features.operators.openshift.io/token-auth-azure": "false",
"features.operators.openshift.io/token-auth-gcp": "false",
"features.operators.openshift.io/token-auth-gcp": "false",
}

annotations, ok := csv["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})
Expand Down Expand Up @@ -114,7 +115,7 @@ func replaceIcon(csv map[string]interface{}) {
spec := s.(map[string]interface{})

iconPath := "../bundle/icons/icon.png"
iconData,err := os.ReadFile(iconPath)
iconData, err := os.ReadFile(iconPath)
if err != nil {
log.Fatal(fmt.Sprintf("Error: Failed to read icon file '%s'", iconPath))
}
Expand All @@ -136,19 +137,26 @@ func recoverFromReplaceImages() {
}
}

func replaceImages(csv map[string]interface{}) {
func replaceImages(csv map[string]interface{}, operatorImage string) {
defer recoverFromReplaceImages()

// Konflux will automatically update the image sha based on the most
// recent builds. We want to peel off the SHA and append it to the Red
// Hat registry so that the bundle image will work when it's available
// there.
konfluxPullSpec := "quay.io/redhat-user-workloads/ocp-isc-tenant/file-integrity-operator-dev@sha256:57d8cf654bfa556d9c488edad96e78f0db3e1c99d57790dcc0f195a7ec0569a8"
if operatorImage != "" {
konfluxPullSpec = operatorImage
}

delimiter := "@"
parts := strings.Split(konfluxPullSpec, delimiter)
if len(parts) > 2 {
log.Fatalf("Error: Failed to safely determine image SHA from Konflux pull spec: %s", konfluxPullSpec)
}
if len(parts) < 2 {
log.Fatalf("Error: Operator image must include digest: %s", konfluxPullSpec)
}
imageSha := parts[1]
registry := "registry.redhat.io/compliance/openshift-file-integrity-rhel8-operator"
redHatPullSpec := registry + delimiter + imageSha
Expand Down Expand Up @@ -184,9 +192,17 @@ func removeRelated(csv map[string]interface{}) {
func main() {
var csv map[string]interface{}

if len(os.Args) < 4 {
log.Fatal("Usage: update_csv.go <manifests-dir> <old-version> <new-version> [operator-image]")
}

manifestsDir := os.Args[1]
oldVersion := os.Args[2]
newVersion := os.Args[3]
operatorImage := ""
if len(os.Args) > 4 {
operatorImage = os.Args[4]
}

csvFilename := getInputCSVFilePath(manifestsDir)
fmt.Println(fmt.Sprintf("Found manifest in %s", csvFilename))
Expand All @@ -196,7 +212,7 @@ func main() {
addRequiredAnnotations(csv)
replaceVersion(oldVersion, newVersion, csv)
replaceIcon(csv)
replaceImages(csv)
replaceImages(csv, operatorImage)
removeRelated(csv)

outputCSVFilename := getOutputCSVFilePath(manifestsDir, newVersion)
Expand Down
4 changes: 3 additions & 1 deletion bundle.openshift.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ARG FIO_OLD_VERSION="1.3.5"
ARG FIO_NEW_VERSION="1.3.6"
ARG OPERATOR_IMAGE=""

FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:v1.22 as builder

Expand All @@ -10,8 +11,9 @@ WORKDIR bundle-hack
# Bring the version variables into scope
ARG FIO_OLD_VERSION
ARG FIO_NEW_VERSION
ARG OPERATOR_IMAGE

RUN go run ./update_csv.go ../bundle/manifests ${FIO_OLD_VERSION} ${FIO_NEW_VERSION}
RUN go run ./update_csv.go ../bundle/manifests ${FIO_OLD_VERSION} ${FIO_NEW_VERSION} "${OPERATOR_IMAGE}"
RUN ./update_bundle_annotations.sh

FROM scratch
Expand Down