Skip to content

Commit

Permalink
Add bundle script for konflux
Browse files Browse the repository at this point in the history
  • Loading branch information
Vincent056 committed Dec 17, 2024
1 parent fed54b4 commit 57368e4
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
21 changes: 21 additions & 0 deletions bundle-hack/update_bundle_annotations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The base version determines the index that the build will be included in, but
# not exactly. It is based on semantics of
# https://docs.engineering.redhat.com/display/CFC/Delivery
# 4.10 means that we will be included in 4.10+ indexes
BASE_OCP_VERSION="4.10"
# We use the stable channel because the Compliance Operator adheres to Tier
# 3 operator lifecycle management
# https://docs.google.com/document/d/18BPe68jhk16-4eYGT6zV-iSNLrFVsuPdVIos24kWaaE/edit
CHANNEL="stable"
ANNOTATIONS_CONTENT=$(cat << EOM
annotations:
com.redhat.openshift.versions: "v${BASE_OCP_VERSION}"
operators.operatorframework.io.bundle.channel.default.v1: '${CHANNEL}'
operators.operatorframework.io.bundle.channels.v1: '${CHANNEL}'
operators.operatorframework.io.bundle.manifests.v1: manifests/
operators.operatorframework.io.bundle.mediatype.v1: registry+v1
operators.operatorframework.io.bundle.metadata.v1: metadata/
operators.operatorframework.io.bundle.package.v1: compliance-operator
EOM
)
echo "$ANNOTATIONS_CONTENT" > ./metadata/annotations.yaml
172 changes: 172 additions & 0 deletions bundle-hack/update_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
# The syntax str | None was added in 3.10
# Remove when UBI9 moves to python 3.10+
from __future__ import annotations

import argparse
import base64
import io
import os
import sys
import json

from ruamel.yaml import YAML


yaml = YAML()
yaml.preserve_quotes = True


parser = argparse.ArgumentParser()
parser.add_argument("manifest_directory", type=str,
help="Path to operator manifest directory.")
parser.add_argument("version", type=str,
help="Version string of the product to update in the manifest.")
args = parser.parse_args()


def main() -> None:
build_manifest_file_path = get_manifest_file_path_from_directory(args.manifest_directory)
if not build_manifest_file_path:
print(f"Failed to find manifest in {args.manifest_directory}")
sys.exit(1)

print(f"Success to find manifest in {build_manifest_file_path}")
with open(build_manifest_file_path) as f:
manifest = yaml.load(f)
print(f"Successfully loaded {build_manifest_file_path} from build.")

add_required_annotations(manifest)
replace_version(manifest)
replace_icon(manifest)
replace_images(manifest)
remove_related_images(manifest)
write_manifest(manifest)
print(f"Successfully updated CSV manifest for {args.version}.")



def get_manifest_file_path_from_directory(d: str) -> str|None:
for filename in os.listdir(d):
if filename.endswith('clusterserviceversion.yaml'):
return os.path.join(d, filename)

def add_required_annotations(m: dict) -> None:
"""
Adds required annotations to the CSV file content represented as a dictionary.
Errors out if 'metadata' or 'annotations' does not exist.
:param m: A dictionary representing the operator CSV manifest.
"""
required_annotations = {
"features.operators.openshift.io/disconnected": "true",
"features.operators.openshift.io/fips-compliant": "true",
"features.operators.openshift.io/proxy-aware": "true",
"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"
}

if 'metadata' not in m:
sys.exit("Error: 'metadata' does not exist in the CSV content.")

if 'annotations' not in m['metadata']:
sys.exit("Error: 'annotations' does not exist within 'metadata' in the CSV content.")

for key, value in required_annotations.items():
m['metadata']['annotations'][key] = value

print("Successfully added required annotations.")

def replace_version(m: dict) -> None:
manifest_version = m['spec']['version']
print(f"Updating version references from {manifest_version}",
f"to {args.version} in manifest.")
m['metadata']['name'] = m['metadata']['name'].replace(manifest_version, args.version)
m['metadata']['annotations']['olm.skipRange'] = m['metadata']['annotations']['olm.skipRange'].replace(manifest_version, args.version)
m['spec']['replaces'] = 'compliance-operator.v' + manifest_version
m['spec']['version'] = args.version
print(f"Successfully updated the operator version references from",
f"{manifest_version} to {args.version} in manifest.")


def replace_icon(m: dict) -> None:
"""Replace the upstream icon with a Red Hat branded image.
Perform an in-place update of the icon data on the manifest.
manifest(dict): A dictionary representing the operator CSV manifest
"""
icon_path = "icons/icon.png"
with io.open(icon_path, "rb") as f:
base64_encoded_icon = base64.b64encode(f.read())
icons = [{"base64data": base64_encoded_icon.decode(), "mediatype": "image/png"}]
m['spec']['icon'] = icons
print(f"Successfully updated the operator image to use icon in {icon_path}.")


def replace_images(m: dict) -> None:

CO_OPERATOR_IMAGE_PULLSPEC = "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator"
CO_CONTENT_IMAGE_PULLSPEC = "quay.io/redhat-user-workloads/ocp-isc-tenant/cac-content-fork"
CO_OPENSCAP_IMAGE_PULLSPEC = "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-openscap"
CO_MUST_GATHER_IMAGE_PULLSPEC = "quay.io/redhat-user-workloads/ocp-isc-tenant/compliance-operator-must-gather"

m['spec']['install']['spec']['deployments'][0]['spec']['template']['spec']['containers'][0]['image'] = CO_OPERATOR_IMAGE_PULLSPEC

container_env = m['spec']['install']['spec']['deployments'][0]['spec']['template']['spec']['containers'][0]['env']
for e in container_env:
if e['name'] == "RELATED_IMAGE_OPERATOR":
e['value'] = CO_OPERATOR_IMAGE_PULLSPEC
if e['name'] == "RELATED_IMAGE_PROFILE":
e['value'] = CO_CONTENT_IMAGE_PULLSPEC
if e['name'] == "RELATED_IMAGE_OPENSCAP":
e['value'] = CO_OPENSCAP_IMAGE_PULLSPEC
if e['name'] == "RELATED_IMAGE_MUST_GATHER":
e['value'] = CO_MUST_GATHER_IMAGE_PULLSPEC

# Decode alm-examples
alm_examples_json = json.loads(m['metadata']['annotations']['alm-examples'])

# Iterate through each item in the list and update the contentImage field
for item in alm_examples_json:
if 'spec' in item and 'contentImage' in item['spec']:
item['spec']['contentImage'] = CO_CONTENT_IMAGE_PULLSPEC
# Update nested scans if present
if 'spec' in item and 'scans' in item['spec']:
for scan in item['spec']['scans']:
if 'contentImage' in scan:
scan['contentImage'] = CO_CONTENT_IMAGE_PULLSPEC

# Encode alm-examples back to JSON string
m['metadata']['annotations']['alm-examples'] = json.dumps(alm_examples_json, indent=2)
print("Successfully updated the operator image to use the new image.")


def remove_related_images(m: dict) -> None:
# Remove relatedImages entirely from the CSV. OSBS will look for container
# images in the manifest and populate them as relatedImages when the bundle
# image is built, so that we don't have to. See
# https://osbs.readthedocs.io/en/latest/users.html#pullspec-locations for
# more information on how OSBS does this.
del m['spec']['relatedImages']
print("Removed relatedImages from operator manifest.")


def write_manifest(m: dict) -> None:
old_csv_filename = get_manifest_file_path_from_directory('manifests')
if not old_csv_filename:
print(f"Failed to find manifest in {args.manifest_directory}")
sys.exit(1)

new_csv_filename = os.path.join('manifests', f"compliance-operator.v{args.version}.clusterserviceversion.yaml")
os.rename(old_csv_filename, new_csv_filename)
print(f"Successfully moved {old_csv_filename} to {new_csv_filename}.")
with open(new_csv_filename, 'w') as f:
yaml.dump(m, f)
print(f"Successfully wrote updated manifest to {new_csv_filename}.")


if __name__ == "__main__":
main()
49 changes: 49 additions & 0 deletions bundle.openshift.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as builder-runner
RUN microdnf install -y python3 python3-pip
RUN pip3 install --upgrade pip && pip3 install ruamel.yaml==0.17.9

# Use a new stage to enable caching of the package installations for local development
FROM builder-runner as builder

ARG CO_VERSION="1.6.1"

COPY ./bundle-hack .
COPY ./bundle/icons ./icons
COPY ./bundle/manifests ./manifests
COPY ./bundle/metadata ./metadata

RUN ./update_csv.py ./manifests ${CO_VERSION}
RUN ./update_bundle_annotations.sh

FROM scratch

LABEL name=openshift-compliance-operator-bundle
LABEL version=${CO_VERSION}
LABEL summary='OpenShift Compliance Operator'
LABEL maintainer='Infrastructure Security and Compliance Team <isc-team@redhat.com>'

LABEL io.k8s.display-name='Compliance Operator'
LABEL io.k8s.description='OpenShift Compliance Operator'

LABEL com.redhat.component=openshift-compliance-operator-bundle-container
LABEL com.redhat.delivery.appregistry=false
LABEL com.redhat.delivery.operator.bundle=true
LABEL com.redhat.openshift.versions="v4.10"


LABEL io.openshift.maintainer.product='OpenShift Container Platform'
LABEL io.openshift.tags=openshift,security,compliance,openscap

LABEL operators.operatorframework.io.bundle.channel.default.v1=stable
LABEL operators.operatorframework.io.bundle.channels.v1=stable
LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=compliance-operator
LABEL License=GPLv2+

# Copy files to locations specified by labels.
COPY --from=builder /manifests /manifests/
COPY --from=builder /metadata /metadata/
COPY bundle/tests/scorecard /tests/scorecard

Binary file added bundle/icons/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 57368e4

Please sign in to comment.