elemental-operator/scripts/elemental-airgap.sh

703 lines
23 KiB
Bash
Executable File

#!/bin/bash
set -e
set -o pipefail
set -o nounset
# global vars
CHART_NAME_OPERATOR=""
ELEMENTAL_OPERATOR_CRDS_CHART_NAME="\$ELEMENTAL_CRDS_CHART"
CHANNEL_IMAGE_VAR=""
CHANNEL_IMAGE_VAL=""
IMAGES_TO_SAVE=""
HAULER_REG_DIR=$(mktemp -d)
HAULER_REG_PID=""
HAULER_REG_DUMMY_FILE="hauler-registry.txt"
: ${CONTAINER_IMAGES_NAME:="elemental-images"}
: ${CONTAINER_IMAGES_FILE:=$CONTAINER_IMAGES_NAME".txt"}
: ${CONTAINER_IMAGES_ARCHIVE:=$CONTAINER_IMAGES_NAME".tar.gz"}
: ${DEBUG:="false"}
: ${HAULER:="false"}
: ${HAULER_STORE:=$(pwd)"/elemental-collection"}
: ${LOCAL_REGISTRY:=\$LOCAL_REGISTRY}
: ${CHART_NAME_CRDS:=$ELEMENTAL_OPERATOR_CRDS_CHART_NAME}
: ${CHART_VERSION:="latest"}
: ${CHANNEL_ONLY:="false"}
: ${CHANNEL_IMAGE_NAME:="\$CHANNEL_IMAGE_NAME"}
: ${SKIP_ARCHIVE_CREATION:="false"}
: ${ALL_CHANNELS:="false"}
print_help() {
cat <<- EOF
Usage: $0 [OPTION] -r LOCAL_REGISTRY ELEMENTAL_OPERATOR_CHART
[-ac|-all-channels] add all defined ManagedOSVersionChannel.
[-c|--crds-chart] Elemental CRDS chart (if URL, will be downloaded).
[-co|--channel-only] just extract and rebuild the ManagedOSVersionChannel container image.
[-cv|--chart-version] specify the chart version (only used if passing chart as URLs).
[-d|--debug] enable debug output on screen.
[-i|--images path] tar.gz gernerated by docker save.
[-h|--help] usage message.
[-ha|--hauler] use hauler to generate the archive (an "Haul") of the Elemental "Collection".
[-l|--image-list path] generated text file with the list of saved images (one image per line).
[-r|--local-registry] registry where to load the images to (used in the next steps).
[-sa|--skip-archive] put the list of images in the $CONTAINER_IMAGES_FILE but skip $CONTAINER_IMAGES_ARCHIVE creation.
ELEMENTAL_OPERATOR_CHART could be either a chart tgz file or an url (in that case will be downloaded first)
it could even be 'dev', 'staging' or 'stable' to allow automatic download of the charts.
Parameters could also be set passing env vars:
ALL_CHANNELS (-ac) : $ALL_CHANNELS
CONTAINER_IMAGES_NAME : $CONTAINER_IMAGES_NAME
CONTAINER_IMAGES_FILE (-l) : $CONTAINER_IMAGES_FILE
CONTAINER_IMAGES_ARCHIVE (-i) : $CONTAINER_IMAGES_ARCHIVE
DEBUG (-d) : $DEBUG
LOCAL_REGISTRY (-r) : $LOCAL_REGISTRY
CHART_NAME_CRDS (-c) : $CHART_NAME_CRDS
CHART_VERSION (-cv) : $CHART_VERSION
CHANNEL_ONLY (-co) : $CHANNEL_ONLY
CHANNEL_IMAGE_NAME : $CHANNEL_IMAGE_NAME
SKIP_ARCHIVE_CREATION (-sa) : $SKIP_ARCHIVE_CREATION
examples:
$0 -r $LOCAL_REGISTRY staging
$0 oci://registry.opensuse.org/isv/rancher/elemental/staging/charts/rancher/elemental-operator-chart \\
-c oci://registry.opensuse.org/isv/rancher/elemental/staging/charts/rancher/elemental-operator-crds-chart \\
-r $LOCAL_REGISTRY
# For creating an Hauler haul
$0 -ha -r $LOCAL_REGISTRY stable
EOF
}
parse_parameters() {
local help="false"
local container_images_archive="false"
POSITIONAL=()
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-i|--images)
CONTAINER_IMAGES_ARCHIVE="$2"
container_images_archive="true"
shift # past argument
shift # past value
;;
-l|--image-list)
CONTAINER_IMAGES_FILE="$2"
shift # past argument
shift # past value
;;
-c|--crds-chart)
CHART_NAME_CRDS="$2"
shift
shift
;;
-cv|--chart-version)
CHART_VERSION="$2"
shift
shift
;;
-r|--local-registry)
LOCAL_REGISTRY="$2"
shift
shift
;;
-co|--channel-only)
CHANNEL_ONLY=true
shift
;;
-sa|--skip-archive)
SKIP_ARCHIVE_CREATION=true
shift
;;
-d|--debug)
DEBUG="true"
shift
;;
-h|--help)
help="true"
shift
;;
-ha|--hauler)
HAULER="true"
if [[ "${container_images_archive}" == "false" ]]; then
CONTAINER_IMAGES_ARCHIVE="elemental-haul.tar.zst"
fi
shift
;;
-ac|--all-channels)
ALL_CHANNELS=true
shift
;;
*)
[[ -n "$CHART_NAME_OPERATOR" ]] && exit_error "unrecognized command: $1"
CHART_NAME_OPERATOR="$1"
shift
;;
esac
done
if [[ "$help" = "true" ]]; then
print_help
exit 0
fi
if [[ -z "$CHART_NAME_OPERATOR" ]]; then
print_help
echo ""
exit_error "ELEMENTAL_OPERATOR_CHART is a required argument"
fi
if [[ "$LOCAL_REGISTRY" == "\$LOCAL_REGISTRY" ]]; then
print_help
echo ""
exit_error "LOCAL_REGISTRY is required"
fi
case "$CHART_NAME_OPERATOR" in
Dev|dev|DEV)
CHART_NAME_OPERATOR="oci://registry.opensuse.org/isv/rancher/elemental/dev/charts/rancher/elemental-operator-chart"
[[ "$CHART_NAME_CRDS" == "$ELEMENTAL_OPERATOR_CRDS_CHART_NAME" ]] && \
CHART_NAME_CRDS="oci://registry.opensuse.org/isv/rancher/elemental/dev/charts/rancher/elemental-operator-crds-chart"
;;
Maintenance|maintenance|MAINTENANCE)
# For now, we have to hardcode the 6.0 path, we will have to add a parameter to choose between 6.0 and 6.1
CHART_NAME_OPERATOR="oci://registry.opensuse.org/isv/rancher/elemental/maintenance/6.0/charts/rancher/elemental-operator-chart"
[[ "$CHART_NAME_CRDS" == "$ELEMENTAL_OPERATOR_CRDS_CHART_NAME" ]] && \
CHART_NAME_CRDS="oci://registry.opensuse.org/isv/rancher/elemental/maintenance/6.0/charts/rancher/elemental-operator-crds-chart"
;;
Staging|staging|STAGING)
CHART_NAME_OPERATOR="oci://registry.opensuse.org/isv/rancher/elemental/staging/charts/rancher/elemental-operator-chart"
[[ "$CHART_NAME_CRDS" == "$ELEMENTAL_OPERATOR_CRDS_CHART_NAME" ]] && \
CHART_NAME_CRDS="oci://registry.opensuse.org/isv/rancher/elemental/staging/charts/rancher/elemental-operator-crds-chart"
;;
Stable|stable|STABLE)
CHART_NAME_OPERATOR="oci://registry.suse.com/rancher/elemental-operator-chart"
[[ "$CHART_NAME_CRDS" == "$ELEMENTAL_OPERATOR_CRDS_CHART_NAME" ]] && \
CHART_NAME_CRDS="oci://registry.suse.com/rancher/elemental-operator-crds-chart"
;;
esac
}
exit_error() {
eval msg=\"$1\"
echo -e "ERR: $msg"
is_hauler && hauler_stop_local_registry
exit 1
}
log_debug() {
[[ "$DEBUG" == "false" ]] && return 0
eval msg=\'${1}\'
echo -e "$msg"
}
log_info() {
eval msg=\"$1\"
echo -e "$msg"
}
is_hauler() {
[[ "${HAULER}" != "false" ]] && return 0 || return 1
}
# get_chart_val "VARNAME" "CHARTVAR" ["false"]
# Retrieves CHARTVAR from the chart and puts the value in VARNAME.
# If CHARTVAR is not found in the chart, it will error and exit, BUT if the optional third parameter
# is passed and is == "false" would just log the missing value and not terminate the script.
get_chart_val() {
local local_var="$1"
local local_val="$2"
local local_fail=${3:-"true"}
local local_condition="[[ \"\$$local_var\" == \"null\" ]]"
eval $local_var='$(helm show values $CHART_NAME_OPERATOR | eval yq '.${local_val}' | sed s/\"//g 2>&1)'
if eval $local_condition; then
if [[ "$local_fail" == "false" ]]; then
log_debug "cannot find $local_val in $CHART_NAME_OPERATOR"
eval $local_var=""
return 0
fi
exit_error "cannot find \$local_val in $CHART_NAME_OPERATOR (likely not an elemental-operator chart with airgap support)"
fi
eval log_debug \"extracted $local_var\\t: \$$local_var\ \($local_val\)\"
}
# get_json_val "VARNAME" "JSONDATA" "JSONFIELD"
# receives a json data snippet as JSONDATA and the json field to retrieve as JSONFIELD.
# puts the value extracted in a variable called as VARNAME.
get_json_val() {
local local_var="$1"
local jsondata="$2"
local local_val="$3"
eval $local_var=$(echo $jsondata | jq $local_val)
eval log_debug \"extracted $local_var\\t: \$$local_var\ \($local_val\)\"
}
# set_json_val "VARNAME" "JSONDATA" "JSONFIELD" "VALUE"
# sets JSONFIELD to VALUE in the received JSONDATA json snippet and outputs the changed json data in
# variable VARNAME
set_json_val() {
local local_var="$1"
local jsondata="$2"
local json_field="$3"
local local_val="$4"
log_debug "set $json_field to $local_val"
eval $local_var=\'$(echo $jsondata | jq $json_field=\"$local_val\")\'
eval log_debug \"\$$local_var\"
}
# add_image_to_export_list
# this just adds the passed image to the list of the images to be saved in the images list text file and
# in the tar.gz archive containing the saved images to be loaded in the local registry.
add_image_to_export_list() {
is_hauler && return 0
local img="${1}"
if [[ -z "${img}" ]]; then
log_debug "cannot add image to export list: empty image passed"
return 0
fi
case "${IMAGES_TO_SAVE} " in
*" ${img} "*)
log_debug "skip adding image to export list: '$img' already added"
;;
*)
log_debug "mark '$img' image for export"
IMAGES_TO_SAVE="$IMAGES_TO_SAVE $img"
;;
esac
}
prereq_checks() {
log_debug "Check required binaries availability"
local cmd_list="helm yq jq sed docker"
is_hauler && cmd_list="${cmd_list} hauler"
for cmd in $cmd_list; do
if ! command -v "$cmd" > /dev/null; then
exit_error "'$cmd' not found."
fi
log_debug "$cmd found"
done
}
fetch_charts() {
local outstr charts="CHART_NAME_OPERATOR"
[[ "$CHART_NAME_CRDS" != "$ELEMENTAL_OPERATOR_CRDS_CHART_NAME" ]] && charts="${charts} CHART_NAME_CRDS"
for c in $charts; do
local chart=""
local chart_ver=""
local opts=""
# helm pull only supports semver tags: for the "latest" tag just don't put the version.
[[ "$CHART_VERSION" != "latest" ]] && chart_ver="--version $CHART_VERSION"
# helm pull needs to be aware of development versions
[[ "${CHART_NAME_OPERATOR}" =~ (Dev|dev|DEV|Staging|staging|STAGING) ]] && opts="--devel"
# 'c' var holds the name (e.g., CHART_NAME_OPERATOR),
# 'chart' var holds the value (e.g., elemental-operator-chart-1.4.tgz)
eval chart=\$$c
case $chart in
"oci://"*|"https//"*)
log_debug "fetching chart '$chart' $chart_ver"
if ! outstr=$(helm pull ${opts} ${chart} $chart_ver 2>&1); then
exit_error "downloading ${chart}:\n $outstr"
fi
eval $c=$(ls -t1 | head -n 1)
log_info "Downloaded Elemental Operator chart: \$$c"
if is_hauler; then
eval hauler_store_add_file "\$$c"
fi
;;
*)
[[ ! -f "$chart" ]] && exit_error "chart file $chart not found"
log_debug "using chart $chart"
;;
esac
done
}
pull_chart_container_images() {
local oprtimg_repo oprtimg_tag seedimg_repo seedimg_tag registry_url
[[ "$CHANNEL_ONLY" == "true" ]] && return 0
get_chart_val oprtimg_repo "image.repository"
get_chart_val oprtimg_tag "image.tag"
get_chart_val seedimg_repo "seedImage.repository"
get_chart_val seedimg_tag "seedImage.tag"
get_chart_val source_registry "registryUrl"
for img in "${oprtimg_repo}:${oprtimg_tag}" "${seedimg_repo}:${seedimg_tag}"; do
[[ -z "$img" ]] && continue
if is_hauler; then
hauler_store_add_image "${source_registry}/${img}"
continue
elif pull_image "${source_registry}/${img}"; then
docker tag "${source_registry}/${img}" "${img}"
add_image_to_export_list "${img}"
fi
done
}
pull_image() {
local image_url="$1"
[[ -z "$image_url" ]] && return 1
if docker pull "$image_url" > /dev/null 2>&1; then
log_info "Image pull success: ${image_url}"
else
if docker inspect "$image_url" > /dev/null 2>&1; then
log_debug "error downloading ${image_url} but the image is saved"
else
log_info "Image pull failed: ${image_url}"
return 1
fi
fi
return 0
}
build_os_channel() {
local channel_img
local channel_tag="latest"
local channel_repo
local channel_list
log_info "Creating OS channel"
# name of the new channel container image we are going to create
if [[ "${CHANNEL_IMAGE_NAME}" == "\$CHANNEL_IMAGE_NAME" ]]; then
CHANNEL_IMAGE_NAME="rancher/elemental-channel-${LOCAL_REGISTRY%:*}"
fi
# defaultChannels has been introduced in 1.7 version
# we can directly add the images in channel_list
get_chart_val channel_list "defaultChannels" "false"
if [ -n "$channel_list" ]; then
get_chart_val channel_list "defaultChannels[].image" "false"
fi
if [[ -z "$channel_list" ]]; then
# v1.4+ chart
#
# channel.repository was changed to channel.image around 1.4 - 1.5 versions
# channel.image contains the full URL (with registry) while channel.repository not (full URL by prepending with registryUrl)
# example, full image URL: registry.suse.com/rancher/elemental-channel
# - operator < v1.4
# . channel.repository = "rancher/elemental-channel"
# . registryUrl = "registry.suse.com"
# - operator > v1.4
# . channel.image = "registry.suse.com/rancher/elemental-channel"
#
# we want in channel_img the full image URL for both cases
get_chart_val channel_img "channel.image" "false"
if [[ -z "$channel_img" ]]; then
# legacy chart
get_chart_val channel_img "channel.repository"
get_chart_val channel_repo "registryUrl"
channel_img=${channel_repo}/${channel_img}
CHANNEL_IMAGE_VAR="channel.repository"
CHANNEL_IMAGE_VAL=${CHANNEL_IMAGE_NAME}
else
CHANNEL_IMAGE_VAR="channel.image"
CHANNEL_IMAGE_VAL=${LOCAL_REGISTRY}/${CHANNEL_IMAGE_NAME}
fi
get_chart_val channel_tag "channel.tag"
channel_list+="${channel_img}:${channel_tag} "
else
# This defined the local channel image
CHANNEL_IMAGE_VAR="channel.image"
CHANNEL_IMAGE_VAL=${LOCAL_REGISTRY}/${CHANNEL_IMAGE_NAME}
fi
# we can have OS channels added in templates, so we have to sync them if needed
if [[ "$ALL_CHANNELS" == "true" ]]; then
# get all ManagedOSVersionChannel, so the already extracted one is in, we can overwrite channel_list
channel_list=$(helm template $CHART_NAME_OPERATOR \
| yq 'select(.kind=="ManagedOSVersionChannel") .spec.options.image' \
| sed -e s/\"//g -e /^---$/d 2>&1)
fi
if [[ -z "${channel_list// /}" ]]; then
log_info "\nWARNING: channel image not found: you will need to provide your own Elemental OS images\n"
return 0
fi
TEMPDIR=$(mktemp -d)
log_debug "build channel image in $TEMPDIR"
pushd $TEMPDIR > /dev/null
# loop on the channel list
for channel in ${channel_list}; do
local_channel_img=${channel%:*}
local_channel_tag=${channel#*:}
log_info "Found channel image: ${local_channel_img}:${local_channel_tag}"
# extract the channel.json
if ! docker run --entrypoint busybox ${local_channel_img}:${local_channel_tag} cat channel.json > channel_${local_channel_img//\//_}_${local_channel_tag}.json; then
exit_error "cannot extract OS images"
fi
done
# Merge all channel_*.json files
if ! jq -s add channel_*.json > channel.json; then
exit_error "cannot merge channel json files"
fi
# write the new channel and identify OS images to save
local new_channel=""
local -i index=0
while true; do
local item item_type item_image item_name item_url_field
item=$(jq .[$index] channel.json)
[[ "$item" == "null" ]] && break
# increment index
index+=1
get_json_val item_name "$item" ".metadata.name"
get_json_val item_type "$item" ".spec.type"
get_json_val item_display "$item" ".spec.metadata.displayName"
# drop the items in the channel which are not containers
case $item_type in
container)
item_url_field=".spec.metadata.upgradeImage"
get_json_val item_image "$item" "$item_url_field"
;;
iso)
item_url_field=".spec.metadata.uri"
get_json_val item_image "$item" "$item_url_field"
case $item_image in
"http://"*|"https://"*)
log_debug "skip OS $item_type entry:\t$item_name\t$item_image"
continue
;;
esac
;;
*)
log_info "ERR: unknown image type `$item_type`, skip OS entry '$item_name'"
continue
;;
esac
log_info "Extract OS image:\n\t$item_name ($item_display)\n\t$item_image"
if [[ "$SKIP_ARCHIVE_CREATION" != "true" ]]; then
# save the OS image
if is_hauler; then
if ! hauler_store_add_image "${item_image}" "neverquit"; then
continue
fi
elif ! pull_image "${item_image}"; then
continue
fi
fi
add_image_to_export_list "${item_image}"
# prepend the private registry name
set_json_val item "$item" "$item_url_field" "${LOCAL_REGISTRY}/${item_image}"
[[ -z "$new_channel" ]] && new_channel="[${item}" || new_channel="${new_channel},${item}"
done
new_channel="${new_channel}]"
local channel_full_url="${CHANNEL_IMAGE_NAME}:${channel_tag}"
is_hauler && channel_full_url=localhost:5000/${channel_full_url}
log_info "Create new channel image for the private registry: ${channel_full_url}"
jq -n "$new_channel" > channel.json
cat << EOF > Dockerfile
FROM registry.suse.com/bci/bci-busybox:latest
ADD channel.json /channel.json
USER 10010:10010
ENTRYPOINT ["busybox", "cp"]
CMD ["/channel.json", "/data/output"]
EOF
docker build . -t ${channel_full_url}
if is_hauler; then
hauler_start_local_registry
docker push ${channel_full_url}
hauler_store_add_image ${channel_full_url}
hauler_stop_local_registry
fi
popd > /dev/null
[[ "$DEBUG" == "false" ]] && rm -rf $TEMPDIR
add_image_to_export_list "${channel_full_url}"
}
hauler_start_local_registry() {
# hauler needs something in the store to start the registry
log_debug "start Hauler registry in $HAULER_REG_DIR"
pushd ${HAULER_REG_DIR} > /dev/null
touch ${HAULER_REG_DUMMY_FILE}
hauler store add file ${HAULER_REG_DUMMY_FILE}
hauler store serve registry > /dev/null 2>&1 &
if (( $? )); then
hauler_stop_local_registry
exit_error "hauler: cannot start registry"
fi
HAULER_REGISTRY_PID=$!
# wait the hauler registry to be up and running
sleep 1
popd > /dev/null
}
hauler_stop_local_registry() {
if [[ -n "{HAULER_REGISTRY_PID}" ]]; then
kill ${HAULER_REGISTRY_PID} > /dev/null 2>&1
fi
rm -rf ${HAULER_REG_DIR}
}
hauler_store_add_file() {
local file="$1"
local neverquit=${2:-""}
if ! hauler store -s ${HAULER_STORE} add file ${file}; then
# !hauler does not return error (yet): https://github.com/rancherfederal/hauler/issues/185
if [[ -z "$neverquit" ]]; then
exit_error "hauler: cannot add file ${file}"
fi
fi
}
hauler_store_add_image() {
local img="$1"
local neverquit=${2:-""}
if ! hauler store -s ${HAULER_STORE} add image ${img}; then
# !hauler does not return error (yet): https://github.com/rancherfederal/hauler/issues/185
if [[ -z "$neverquit" ]]; then
exit_error "hauler: cannot add image ${img}"
fi
fi
}
create_container_images_archive() {
if is_hauler; then
log_info "Creating haul ${CONTAINER_IMAGES_ARCHIVE}"
hauler store -s ${HAULER_STORE} save -f ${CONTAINER_IMAGES_ARCHIVE}
return 0
fi
echo -n "" > "${CONTAINER_IMAGES_FILE}"
for i in ${IMAGES_TO_SAVE} ; do
log_debug "* $i"
echo "$i" >> "${CONTAINER_IMAGES_FILE}"
done
sort -u ${CONTAINER_IMAGES_FILE} -o ${CONTAINER_IMAGES_FILE}
[[ "$SKIP_ARCHIVE_CREATION" == "true" ]] && return 0
log_info "Creating ${CONTAINER_IMAGES_ARCHIVE} with $(echo ${IMAGES_TO_SAVE} | wc -w | tr -d '[:space:]') images (may take a while)"
docker save $(echo ${IMAGES_TO_SAVE}) | gzip --stdout > ${CONTAINER_IMAGES_ARCHIVE}
}
print_next_steps() {
local registry_url
[[ "$SKIP_ARCHIVE_CREATION" == "true" ]] && return 0
get_chart_val registry_url "registryUrl"
if is_hauler; then
print_next_steps_hauler
else
print_next_steps_docker
fi
}
print_next_steps_docker() {
cat <<- EOF
NEXT STEPS:
1) Load the '$CONTAINER_IMAGES_ARCHIVE' to the local registry ($LOCAL_REGISTRY)
available in the airgapped infrastructure:
./rancher-load-images.sh \\
--image-list $CONTAINER_IMAGES_FILE \\
--images $CONTAINER_IMAGES_ARCHIVE \\
--registry $LOCAL_REGISTRY
2) Install the elemental charts downloaded in the current directory passing the local registry
and the newly created channel image:
helm upgrade --create-namespace -n cattle-elemental-system --install elemental-operator-crds $CHART_NAME_CRDS
helm upgrade --create-namespace -n cattle-elemental-system --install elemental-operator $CHART_NAME_OPERATOR \\
--set registryUrl=$LOCAL_REGISTRY \\
--set $CHANNEL_IMAGE_VAR=$CHANNEL_IMAGE_VAL
EOF
}
print_next_steps_hauler() {
cat <<- EOF
NEXT STEPS:
1) Load the '$CONTAINER_IMAGES_ARCHIVE' Haul archive in the Hauler instance in the airgapped infrastructure:
hauler store load '$CONTAINER_IMAGES_ARCHIVE'
2) If the local registry is not served by Hauler, copy the content of the Haul archive to the local registry:
hauler store copy registry://$LOCAL_REGISTRY
for more options and information on Hauler, check the official docs:
https://rancherfederal.github.io/hauler-docs/
3) Extract the elemental charts from the Hauler store in the airgapped infrastructure:
hauler store extract $CHART_NAME_CRDS
hauler store extract $CHART_NAME_OPERATOR
and install them passing the local registry and the newly created channel image:
helm upgrade --create-namespace -n cattle-elemental-system --install elemental-operator-crds $CHART_NAME_CRDS
helm upgrade --create-namespace -n cattle-elemental-system --install elemental-operator $CHART_NAME_OPERATOR \\
--set registryUrl=$LOCAL_REGISTRY \\
--set $CHANNEL_IMAGE_VAR=$CHANNEL_IMAGE_VAL
EOF
}
clean_up() {
is_hauler || return 0
# only hauler stuffs need to be cleaned at the end
[[ "$DEBUG" == "false" ]] && \
rm -rf ${HAULER_STORE} ${CHART_NAME_CRDS} ${CHART_NAME_OPERATOR}
}
parse_parameters "$@"
prereq_checks
fetch_charts
pull_chart_container_images
build_os_channel
create_container_images_archive
print_next_steps
clean_up