From c706ebc6a194bed699d1e330208abc7ac1faeaed Mon Sep 17 00:00:00 2001 From: Per Goncalves da Silva Date: Wed, 21 Aug 2024 16:47:44 -0300 Subject: [PATCH] tools Signed-off-by: Per Goncalves da Silva --- hack/tools/catalogs/bundle-manifests | 126 ++++++++++++++++ hack/tools/catalogs/download-catalog | 61 ++++++++ hack/tools/catalogs/lib/bundle.sh | 117 +++++++++++++++ hack/tools/catalogs/lib/collect-rbac.sh | 155 ++++++++++++++++++++ hack/tools/catalogs/lib/hash.sh | 37 +++++ hack/tools/catalogs/lib/manifests.sh | 115 +++++++++++++++ hack/tools/catalogs/lib/rbac.sh | 114 ++++++++++++++ hack/tools/catalogs/lib/unpack.sh | 34 +++++ hack/tools/catalogs/lib/utils.sh | 5 + hack/tools/catalogs/list-compatible-bundles | 77 ++++++++++ 10 files changed, 841 insertions(+) create mode 100755 hack/tools/catalogs/bundle-manifests create mode 100755 hack/tools/catalogs/download-catalog create mode 100644 hack/tools/catalogs/lib/bundle.sh create mode 100644 hack/tools/catalogs/lib/collect-rbac.sh create mode 100644 hack/tools/catalogs/lib/hash.sh create mode 100644 hack/tools/catalogs/lib/manifests.sh create mode 100644 hack/tools/catalogs/lib/rbac.sh create mode 100644 hack/tools/catalogs/lib/unpack.sh create mode 100644 hack/tools/catalogs/lib/utils.sh create mode 100755 hack/tools/catalogs/list-compatible-bundles diff --git a/hack/tools/catalogs/bundle-manifests b/hack/tools/catalogs/bundle-manifests new file mode 100755 index 000000000..fc3662703 --- /dev/null +++ b/hack/tools/catalogs/bundle-manifests @@ -0,0 +1,126 @@ +#!/usr/bin/env bash + +# Get the directory of the current script +SCRIPT_ROOT=$(dirname "$(realpath "$0")") + +source "${SCRIPT_ROOT}/lib/unpack.sh" +source "${SCRIPT_ROOT}/lib/collect-rbac.sh" + +# Set the container runtime, default to docker +CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-docker} + +function usage() { + script_name=$(basename "$0") + echo "Usage:" + echo "./${script_name} install [-n namespace] [-e cluster-extension-name] [-cr cluster-role-name] [-r role-name] [-s service-account-name] [--use-defaults]" + echo "./${script_name} rbac [-n namespace] [-e cluster-extension-name] [-cr cluster-role-name] [-r role-name] [-s service-account-name] [--use-defaults]" + echo "" + echo "Note: If --use-defaults is not set, the unset optional parameters will appear as template variables in the resulting manifests, i.e.:" + echo "\${NAMESPACE}, \${EXTENSION_NAME}, \${CLUSTER_ROLE_NAME}, \${ROLE_NAME}, \${SERVICE_ACCOUNT_NAME}." + echo "If --use-defaults is set, the defaults will be applied only to optional parameters that are not set." + echo "Defaults: cluster-extension-name => , namespace => -system, " + echo "service-account => -installer, cluster role => -cluster-role," + echo "role => -installer-role." + echo "NOTE: The role and cluster-role bindings always follow the format <[cluster-]role-name>-binding." +} + +# Check for at least 3 arguments +if [ "$#" -lt 3 ]; then + usage + exit 1 +fi + +# Command and package details +COMMAND=$1 +export PACKAGE_NAME=$2 +export PACKAGE_VERSION=$3 + +# Initialize environment variables with template defaults +export NAMESPACE="\${NAMESPACE}" +export EXTENSION_NAME="\${EXTENSION-NAME}" +export CLUSTER_ROLE_NAME="\${CLUSTER_ROLE_NAME}" +export ROLE_NAME="\${ROLE_NAME}" +export SERVICE_ACCOUNT_NAME="\${SERVICE_ACCOUNT_NAME}" +use_defaults=false + +# Parse optional arguments +shift 3 +while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + -n) + export NAMESPACE="$2" + shift 2 + ;; + -e) + export EXTENSION_NAME="$2" + shift 2 + ;; + -cr) + export CLUSTER_ROLE_NAME="$2" + shift 2 + ;; + -r) + export ROLE_NAME="$2" + shift 2 + ;; + -s) + export SERVICE_ACCOUNT_NAME="$2" + shift 2 + ;; + --use-defaults) + use_defaults=true + shift + ;; + *) + echo "Unknown option $1" + usage + exit 1 + ;; + esac +done + +# Apply default values only to unset parameters if --use-defaults is set +if [ "$use_defaults" = true ]; then + [ "$EXTENSION_NAME" == "\${EXTENSION-NAME}" ] && export EXTENSION_NAME="${PACKAGE_NAME}" + [ "$NAMESPACE" == "\${NAMESPACE}" ] && export NAMESPACE="${EXTENSION_NAME}-system" + [ "$SERVICE_ACCOUNT_NAME" == "\${SERVICE_ACCOUNT_NAME}" ] && export SERVICE_ACCOUNT_NAME="${PACKAGE_NAME}-installer" + [ "$CLUSTER_ROLE_NAME" == "\${CLUSTER_ROLE_NAME}" ] && export CLUSTER_ROLE_NAME="${SERVICE_ACCOUNT_NAME}-cluster-role" + [ "$ROLE_NAME" == "\${ROLE_NAME}" ] && export ROLE_NAME="${SERVICE_ACCOUNT_NAME}-installer-role" +fi + +# Output the set environment variables for confirmation +debug "Environment variables set:" +debug "NAMESPACE=${NAMESPACE}" +debug "EXTENSION_NAME=${EXTENSION_NAME}" +debug "CLUSTER_ROLE_NAME=${CLUSTER_ROLE_NAME}" +debug "ROLE_NAME=${ROLE_NAME}" +debug "SERVICE_ACCOUNT_NAME=${SERVICE_ACCOUNT_NAME}" + +# Find bundle image +image="$(cat - | get-bundle-image "${PACKAGE_NAME}" "${PACKAGE_VERSION}")" + +# Unpack and close container +bundle_manifest_dir="$(unpack "$(create_container "${image}")")" + +# Derive rbac from bundle manifests +collect_installer_rbac "${bundle_manifest_dir}" + +# Example output or further processing based on command +case "${COMMAND}" in + install) + generate_install_manifests | envsubst + ;; + rbac) + generate_rbac_manifests | envsubst + ;; + *) + echo "Unknown command $command" + usage + exit 1 + ;; +esac + +# Clean up manifest directory +rm -rf "${bundle_manifest_dir}" \ No newline at end of file diff --git a/hack/tools/catalogs/download-catalog b/hack/tools/catalogs/download-catalog new file mode 100755 index 000000000..f8095946e --- /dev/null +++ b/hack/tools/catalogs/download-catalog @@ -0,0 +1,61 @@ +#!/bin/bash + +# Constants +SERVICE_NAMESPACE="olmv1-system" +SERVICE_NAME="catalogd-catalogserver" +LOCAL_PORT=8001 +SERVICE_PORT=443 # Assuming the service uses HTTPS on port 443 + +# Function to display usage +usage() { + echo "Usage: $0 " + exit 1 +} + +# Check if catalog name is provided +if [ -z "$1" ]; then + usage +fi + +CATALOG_NAME="$1" + +# Check if the clustercatalog resource exists +echo "Checking if ClusterCatalog $CATALOG_NAME exists..." +CLUSTER_CATALOG=$(kubectl get clustercatalog "$CATALOG_NAME" -o json 2>/dev/null) +if [ -z "$CLUSTER_CATALOG" ]; then + echo "ClusterCatalog $CATALOG_NAME does not exist." + exit 1 +fi + +# Check if the Unpacked condition is true +UNPACKED_CONDITION=$(echo "$CLUSTER_CATALOG" | jq -r '.status.conditions[] | select(.type=="Unpacked") | .status') +if [ "$UNPACKED_CONDITION" != "True" ]; then + echo "ClusterCatalog $CATALOG_NAME is not unpacked yet." + exit 1 +fi + +# Get the contentURL +CONTENT_URL=$(echo "$CLUSTER_CATALOG" | jq -r '.status.contentURL') +if [ -z "$CONTENT_URL" ]; then + echo "Content URL not found for ClusterCatalog $CATALOG_NAME." + exit 1 +fi + +# Start port forwarding +echo "Starting kubectl port-forward to $SERVICE_NAME on port $LOCAL_PORT..." +kubectl port-forward -n "$SERVICE_NAMESPACE" svc/"$SERVICE_NAME" $LOCAL_PORT:$SERVICE_PORT &>/dev/null & +PORT_FORWARD_PID=$! +sleep 2 # Wait for the port-forwarding to start + +# Modify the contentURL to hit localhost: +LOCAL_CONTENT_URL=$(echo "$CONTENT_URL" | sed "s|https://[^/]*|https://localhost:$LOCAL_PORT|") + +# Download the catalog using wget +echo "Downloading catalog from $LOCAL_CONTENT_URL..." +wget --no-check-certificate "$LOCAL_CONTENT_URL" -O "${CATALOG_NAME}-catalog.json" + +# Stop the port forwarding +echo "Stopping kubectl port-forward..." +kill $PORT_FORWARD_PID + +echo "Catalog downloaded to ${CATALOG_NAME}-catalog.json" \ No newline at end of file diff --git a/hack/tools/catalogs/lib/bundle.sh b/hack/tools/catalogs/lib/bundle.sh new file mode 100644 index 000000000..ee1d3a720 --- /dev/null +++ b/hack/tools/catalogs/lib/bundle.sh @@ -0,0 +1,117 @@ +source "${SCRIPT_ROOT}/lib/hash.sh" + +# Given package and version grabs the bundle image from stdin FBC stream +function get-bundle-image(){ + local package_name="${1}" + local package_version="${2}" + local image=$(jq -r --arg pkg "$package_name" --arg ver "$package_version" \ + 'select(.schema == "olm.bundle" and (.properties[] | select(.type == "olm.package" and .value.packageName == $pkg and .value.version == $ver))) | .image') + + if [ -z "$image" ]; then + echo "No matching image found for package '$package_name' with version '$package_version'." + exit 1 + fi + + echo "${image}" +} + +# Function to validate the bundle is supported +function check-bundle-supported() { + local csv="${1}" + local valid=$(yq eval ' + (.spec.installModes[] | select(.type == "AllNamespaces" and .supported == true)) and + ((.spec.webhookdefinitions == null) or (.spec.webhookdefinitions | length == 0)) and + ((.spec.customresourcedefinitions.required == null) or (.spec.customresourcedefinitions.required | length == 0)) + ' "${csv}") + + # Check if the result is 'true' + if [ "$valid" != "true" ]; then + echo "Bundles with dependencies, or that don't support AllNamespaces mode, or with webhookdefinitions, are not supported." + exit 1 + fi + +} + +# Function to get all resource names for a particular kind +# from the manifest directory +function collect_resource_names() { + local manifest_dir="${1}" + local kind="${2}" + local resource_names=() + while IFS= read -r resource_file; do + name=$(yq eval -r '.metadata.name' "$resource_file") + if [ -n "$name" ]; then + resource_names+=("$name") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "${resource_names[@]}" +} + +# Function that collects all the rules for all the ClusterRole manifests +# shipped with the bundle +function collect_manifest_cluster_role_perms() { + local manifest_dir="${1}" + local kind="ClusterRole" + local all_cr_rules="[]" + + while IFS= read -r resource_file; do + # Extract the entire rules array from the current file and ensure it's treated as a valid JSON array + cr_rules=$(yq eval -o=json -r '.rules // []' "$resource_file") + # Validate and merge the current rules array with the cumulative rules array + if jq -e . >/dev/null 2>&1 <<<"$cr_rules"; then + all_cr_rules=$(jq -c --argjson existing "$all_cr_rules" --argjson new "$cr_rules" '$existing + $new' <<<"$all_cr_rules") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "$all_cr_rules" +} + +# Function that collects all the rules for all the Role manifests +# shipped with the bundle +function collect_manifest_role_perms() { + local manifest_dir="${1}" + local kind="Role" + local all_cr_rules="[]" + + while IFS= read -r resource_file; do + # Extract the entire rules array from the current file and ensure it's treated as a valid JSON array + cr_rules=$(yq eval -o=json -r '.rules // []' "$resource_file") + # Validate and merge the current rules array with the cumulative rules array + if jq -e . >/dev/null 2>&1 <<<"$cr_rules"; then + all_cr_rules=$(jq -c --argjson existing "$all_cr_rules" --argjson new "$cr_rules" '$existing + $new' <<<"$all_cr_rules") + fi + done < <(find "${manifest_dir}" -type f -exec grep -l "^kind: ${kind}" {} \;) + echo "$all_cr_rules" +} + +# Function to get the apiGroup for a named resource of a given kind +# from the manifests dir +function get_api_group() { + local dir_path="$1" + local kind="$2" + local name="$3" + + # Find the file containing the specified kind and name + local file=$(grep -rl "kind: $kind" "$dir_path" | xargs grep -l "name: $name") + + # Extract the apiGroup from the found file + if [ -n "$file" ]; then + local api_group=$(yq eval '.apiVersion' "$file" | awk -F'/' '{print $1}') + echo "$api_group" + fi +} + +# Function to get the generated clusterrole resource names +function generated_cluster_role_names() { + local csvFile="${1}" + local generated_cluster_role_names=() + csv_name=$(yq eval -r '.metadata.name' "${csvFile}") + cperms=$(yq eval -o=json -r '.spec.install.spec.clusterPermissions?' "$csvFile" | jq -c '.[] | {serviceAccountName, rules: [.rules[] | {verbs, apiGroups, resources, resourceNames, nonResourceURLs} | with_entries(select(.value != null and .value != []))]}') + rbacPerms=$(yq eval -o=json -r '.spec.install.spec.permissions?' "$csvFile" | jq -c '.[] | {serviceAccountName, rules: [.rules[] | {verbs, apiGroups, resources, resourceNames, nonResourceURLs} | with_entries(select(.value != null and .value != []))]}') + allPerms=("${cperms[@]}" "${rbacPerms[@]}") + for perm in ${allPerms[@]}; do + local sa=$(echo $perm | yq eval -r '.serviceAccountName') + local generated_name=$(generate_name "${csv_name}-${sa}" "${perm}") + generated_cluster_role_names+=("${generated_name}") + done + echo "${generated_cluster_role_names[@]}" +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/collect-rbac.sh b/hack/tools/catalogs/lib/collect-rbac.sh new file mode 100644 index 000000000..3e3862563 --- /dev/null +++ b/hack/tools/catalogs/lib/collect-rbac.sh @@ -0,0 +1,155 @@ +source "${SCRIPT_ROOT}/lib/utils.sh" +source "${SCRIPT_ROOT}/lib/rbac.sh" +source "${SCRIPT_ROOT}/lib/manifests.sh" +source "${SCRIPT_ROOT}/lib/bundle.sh" + +# Function to add the specified rules +function add_required_rules() { + local finalizer_perm=$(make_rbac_rule "olm.operatorframework.io" "clusterextensions/finalizers" '"update"' "$EXTENSION_NAME") + aggregate_rules "${finalizer_perm}" "cluster" +} + +function collect_crd_rbac() { + debug "Collecting CRD permissions" + local csv="${1}" + crds=($(yq eval -o=json -r '.spec.customresourcedefinitions.owned[]?.name' "$csv")) + add_rbac_rules "apiextensions.k8s.io" "customresourcedefinitions" "cluster" "${ALL_CRD_VERBS}" "${NAMED_CRD_VERBS}" "${crds[@]}" +} + +function collect_cluster_role_rbac() { + local manifest_dir="${1}" + local csv="${2}" + debug "Adding ClusterRole RBAC" + + # Collect shipped ClusterRole names + # And the OLMv1 generated ClusterRole names + manifest_cluster_role_names=($(collect_resource_names "${manifest_dir}" "ClusterRole")) + generated_cluster_role_names=($(generated_cluster_role_names "${csv}")) + all_cluster_role_names=("${manifest_cluster_role_names[@]}" "${generated_cluster_role_names[@]}") + + add_rbac_rules "rbac.authorization.k8s.io" "clusterroles" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_cluster_role_names[@]}" + + # Add all rules for defined in shipped ClusterRoles + # This allows the installer service account to grant rbac + manifest_cr_perms="$(collect_manifest_cluster_role_perms "${manifest_dir}")" + for item in $(echo "$manifest_cr_perms" | jq -c -r '.[]'); do + aggregate_rules "${item}" "cluster" + done + + debug "Adding ClusterPermissions" + # Add all cluster scoped rules for defined in the CSV + # This allows the installer service account to grant rbac + cluster_permissions=$(yq eval -o=json '.spec.install.spec.clusterPermissions[].rules?' "$csv" | jq -c '.[]') + for perm in "${cluster_permissions[@]}"; do + aggregate_rules "${perm}" "cluster" + done + + # Collect RBAC for cluster scoped manifest objects + collect_cluster_scoped_resource_rbac "${manifest_dir}" + + debug "Adding ClusterRoleBinding RBAC" + # Collect shipped ClusterRoleBinding names + # And the OLMv1 generated ClusterRoleBinding names (same as the generated ClusterRole names) + manifest_cluster_role_binding_names=($(collect_resource_names "${manifest_dir}" "ClusterRoleBinding")) + all_cluster_role_binding_names=("${manifest_cluster_role_binding_names[@]}" "${generated_cluster_role_names[@]}") + add_rbac_rules "rbac.authorization.k8s.io" "clusterrolebindings" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_cluster_role_binding_names[@]}" +} + +function collect_cluster_scoped_resource_rbac() { + debug "Adding other ClusterScoped Resources" + local manifest_dir="${1}" + for kind in "${CLUSTER_SCOPED_RESOURCES[@]}"; do + resource_names=($(collect_resource_names "${manifest_dir}" "${kind}")) + if [ ${#resource_names[@]} -eq 0 ]; then + continue + fi + api_group=$(get_api_group "${manifest_dir}" "${kind}" "${resource_names[1]}") + add_rbac_rules "${api_group}" "${kind,,}s" "cluster" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${resource_names[@]}" + done +} + +function collect_operator_deployment_rbac() { + local manifest_dir="${1}" + local csv="${2}" + + debug "Adding Deployment RBAC" + manifest_dep_names=($(collect_resource_names "${manifest_dir}" "Deployment")) + csv_deployments=($(yq eval -o=json -r '.spec.install.spec.deployments[]?.name' "$csv")) + all_deployments=("${manifest_dep_names[@]}" "${csv_deployments[@]}") + add_rbac_rules "apps" "deployments" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_deployments[@]}" +} + +function collect_service_account_rbac() { + debug "Adding ServiceAccount RBAC" + local manifest_dir="${1}" + local csv="${2}" + manifest_sas=($(collect_resource_names "${manifest_dir}" "ServiceAccount")) + csv_sas=($(yq eval '.. | select(has("serviceAccountName")) | .serviceAccountName' "${csv}" | sort -u)) + all_sas=("${manifest_sas[@]}" "${csv_sas[@]}") + add_rbac_rules "" "serviceaccounts" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${all_deployments[@]}" +} + +function collect_role_rbac() { + debug "Collecting Role RBAC" + local manifest_dir="${1}" + local csv="${2}" + + # Shipped Role manifest permissions + manifest_role_perms="$(collect_manifest_role_perms "${manifest_dir}")" + for item in $(echo "$manifest_role_perms" | jq -c -r '.[]'); do + aggregate_rules "${item}" "namespace" + done + + # CSV namespaced permissions + namespace_permissions=$(yq eval -o=json '.spec.install.spec.permissions[].rules?' "$csv" | jq -c '.[]') + for perm in "${namespace_permissions[@]}"; do + aggregate_rules "${perm}" "cluster" + done + + # Account for all other shipped namespace scoped resources + for kind in "${NAMESPACE_SCOPED_RESOURCES[@]}"; do + resource_names=($(collect_resource_names "${manifest_dir}" "${kind}")) + if [ ${#resource_names[@]} -eq 0 ]; then + continue + fi + api_group=$(get_api_group "${manifest_dir}" "${kind}" "${resource_names[@]}") + add_rbac_rules "${api_group}" "${kind,,}s" "namespace" "${ALL_RESOURCE_VERBS}" "${NAMED_RESOURCE_VERBS}" "${resource_names[@]}" + done +} + +function find_csv() { + local manifest_dir="${1}" + local csv_files + + # Use grep -l to find files containing "kind: ClusterServiceVersion" + csv_files=$(grep -l "kind: ClusterServiceVersion" "$manifest_dir"/*.yaml) + + # Check if multiple CSV files are found + if [ $(echo "$csv_files" | wc -l) -gt 1 ]; then + echo "Error: Multiple CSV files found in ${manifest_dir}." + return 1 + elif [ -z "$csv_files" ]; then + echo "Error: No CSV file found in ${manifest_dir}." + return 1 + else + echo "${csv_files}" + fi +} + +function collect_installer_rbac() { + local manifest_dir="${1}" + local csv="$(find_csv "${manifest_dir}")" + + # Check the bundle is supported + check-bundle-supported "${csv}" + + # Add the required permissions rules + add_required_rules + + # Grab CSV name + collect_crd_rbac "${csv}" + collect_cluster_role_rbac "${manifest_dir}" "${csv}" + collect_operator_deployment_rbac "${manifest_dir}" "${csv}" + collect_service_account_rbac "${manifest_dir}" "${csv}" + collect_role_rbac "${manifest_dir}" "${csv}" +} diff --git a/hack/tools/catalogs/lib/hash.sh b/hack/tools/catalogs/lib/hash.sh new file mode 100644 index 000000000..277811ce8 --- /dev/null +++ b/hack/tools/catalogs/lib/hash.sh @@ -0,0 +1,37 @@ +# This file contains functions that reproduce the hashing behavior in internal/rukpak/convert/registryv1.go:generate_name() + +function base36encode() { + local bigint=$(echo "ibase=16; $1" | bc) + echo "$(bc<<<"obase=36;${bigint}" | awk '{for (i=1; i<=NF; i++) printf "%c", $i+(($i<10)?48:(($i<36)?87:29))}')" +} + +function deep_hash_object() { + local obj="$1" + + # Compute SHA-224 hash and convert to uppercase for consistency + local hash_hex=$(echo "$obj" | sha224sum | awk '{print toupper($1)}') + + # Encode the hash to base36 + local base36_hash="$(base36encode "${hash_hex}")" + echo "${base36_hash}" +} + +# Function to generate a name based on the base string and hashed object +function generate_name() { + local base="$1" + local obj="$2" + local max_name_length=63 # Define the maximum name length (similar to DNS limits) + + # Generate a hash from the object using deep_hash_object function + local hash_str + hash_str=$(deep_hash_object "$obj") + + # Check if the combined length exceeds the maximum length + if [ $((${#base} + ${#hash_str})) -gt $max_name_length ]; then + # Truncate the base string + base="${base:0:$(($max_name_length - ${#hash_str} - 1))}" + fi + + # Return the concatenated string + echo "${base}-${hash_str}" +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/manifests.sh b/hack/tools/catalogs/lib/manifests.sh new file mode 100644 index 000000000..a2b863408 --- /dev/null +++ b/hack/tools/catalogs/lib/manifests.sh @@ -0,0 +1,115 @@ +# Function to generate the target install namespace +function generate_namespace() { + cat </dev/null) + if [ -z "$container_id" ]; then + echo "Failed to create container from image '$image'." + exit 1 + fi + + echo "${container_id}" +} + +function unpack() { + local container_id="${1}" + + # Extract the directory from the "operators.operatorframework.io.bundle.manifests.v1" label + local manifest_dir=$($CONTAINER_RUNTIME inspect --format '{{ index .Config.Labels "operators.operatorframework.io.bundle.manifests.v1" }}' "$container_id") + + if [ -z "$manifest_dir" ]; then + echo "No manifest directory label found on the image." + $CONTAINER_RUNTIME rm "$container_id" + exit 1 + fi + + # Copy files from the container to a temporary directory + output_dir=$(mktemp -d) + $CONTAINER_RUNTIME cp "$container_id:$manifest_dir/." "$output_dir" > /dev/null + + # Clean up the container + $CONTAINER_RUNTIME rm "$container_id" > /dev/null + + echo "${output_dir}" +} \ No newline at end of file diff --git a/hack/tools/catalogs/lib/utils.sh b/hack/tools/catalogs/lib/utils.sh new file mode 100644 index 000000000..f23522a28 --- /dev/null +++ b/hack/tools/catalogs/lib/utils.sh @@ -0,0 +1,5 @@ +function debug() { + if [ "${DEBUG,,}" != "false" ] && [ -n "$DEBUG" ]; then + echo "DEBUG: $1" >&2 + fi +} \ No newline at end of file diff --git a/hack/tools/catalogs/list-compatible-bundles b/hack/tools/catalogs/list-compatible-bundles new file mode 100755 index 000000000..65754ad53 --- /dev/null +++ b/hack/tools/catalogs/list-compatible-bundles @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# Run the jq query to filter and then collect the results into an array +# TODO: figure out how to filter out bundles with webhooks +# TODO: The query is not working as expected. There seems to be a problem with the group-by +usage() { + echo "Usage: $0 [-r ] " + echo " -r Optional regex to filter by packageName" + exit 1 +} + +# Parse the optional regex argument +REGEX="" +while getopts "r:" opt; do + case ${opt} in + r ) + REGEX=$OPTARG + ;; + \? ) + usage + ;; + esac +done +shift $((OPTIND -1)) + +#!/usr/bin/env bash + +# Select bundle documents +function looking-for-a-bundle() { + jq 'select(.schema == "olm.bundle")' +} + +# Select bundles that declare AllNamespace install mode +# or declare nothing at all (older released bundles sans "olm.csv.metadata" property) +function in-finance() { + jq 'select( + all(.properties[].type; . != "olm.csv.metadata") or + (.properties[]? | + select(.type == "olm.csv.metadata" and + (.value.installModes[]? | + select(.type == "AllNamespaces" and .supported == true) + ) + ) + ) + )' +} + +# Select bundles without dependencies +function with-a-trust-fund() { + jq 'select(all(.properties[].type; . != "olm.package.required" and . != "olm.gvk.required"))' +} + +# Select the "olm.package" property from the bundles +# This contains the packageName and version information +function six-five() { + jq '.properties[] | select(.type == "olm.package") |.value' +} + +# Group packages by name and collect versions into an array +function blue-eyes() { + jq -s 'group_by(.packageName) | map({packageName: .[0].packageName, versions: map(.version)})' | regex_it +} + +# Apply regex on name +function regex_it() { + jq --arg regex "$REGEX" ' + if $regex != "" then + map(select(.packageName | test($regex))) + else + . + end' +} + +cat - | looking-for-a-bundle | in-finance | with-a-trust-fund | six-five | blue-eyes + +echo "NOTE: OLM v1 currently only supports bundles that support AllNamespaces install model, don't have dependencies, and don't contain webhooks" >&2 +echo "WARNING: These results may include bundles with webhooks, which are incompatible" >&2 \ No newline at end of file