Skip to content

Instantly share code, notes, and snippets.

@vrabbi
Last active October 14, 2024 15:35
Show Gist options
  • Save vrabbi/4ef10b37365c8aa35449b714d6f1128c to your computer and use it in GitHub Desktop.
Save vrabbi/4ef10b37365c8aa35449b714d6f1128c to your computer and use it in GitHub Desktop.

Pack Builder CLI

This is a bash script to assist with creating and publishing SpectroCloud Packs to an OCI Registry

Installation

Download the pack-bulder file and make it executable and save it in your PATH.

Usage - Interactive Mode

Initialize the repo structure

pack-builder create registry-layout

This will ask you for a name, fqdn, user and password for the registry.

Create a new Pack (Helm or manifest based)

pack-builder create pack

This command must be run from within the registry folder created above

Publish a Pack

pack-builder push pack

This command must be run from within the registry folder created above

Install Dependencies

If the commands fail do to missing dependencies, they can be installed using

pack-builder install-dependencies

This assisted installation requires brew to already be installed

Usage - Non-Interactive Mode

This script also supports a non interactive mode using a declarative YAML file for all commands except install-dependencies which is always non interactive.

To work with the non-interactive mode the first arguments must be --config followed by the path to the yaml file providing the needed inputs.

Example YAML files can be found in this GIST for the 4 main commands:

Create the Registry Layout

pack-builder --config registry-values.yaml create registry-layout

Create a Helm based pack

pack-builder --config helm-values.yaml create pack

Create a YAML Manifest based pack

pack-builder --config manifest-values.yaml create pack

Push a pack to a registry

pack-builder --config push-values.yaml push pack
pack:
type: "Helm"
name: "demo"
version: "1.0.0"
display_name: "My Helm Pack"
repo:
name: "aaa"
url: "https://charts.bitnami.com/bitnami"
charts:
- "bitnami/apache"
- "bitnami/mysql"
chart_versions:
"bitnami/apache": "11.2.20"
"bitnami/mysql": "11.1.17"
layer: "addon"
addon_type: "system app"
k8s_version:
min: "1.18"
max: "1.32"
dependencies:
- name: "test"
layer: "addon"
type: "required"
namespace: "my-namespace"
pack:
type: "YAML Manifests"
name: "demo-manifests"
version: "1.0.0"
display_name: "My Manifests Pack"
manifest_sources:
- type: "HTTP URL"
url: "https://github.com/vrabbi-educates/sample-workshops/releases/download/0.0.2/crossplane.yaml"
- type: "Git Repository"
url: "https://github.com/vrabbi/java-web-app"
branch: "main"
content_type: "Specific file"
file_path: "config/workload.yaml"
- type: "Git Repository"
url: "https://github.com/vrabbi/tap-oss"
branch: "main"
content_type: "Sub-folder"
sub_folder: "example-values"
- type: "Git Repository"
url: "https://github.com/vrabbi-carvel/package-for-event-router"
branch: "main"
content_type: "Whole repository"
- type: "Local File"
path: "/home/k8s/educates/educates-workshops/crossplane-workshop/external-workshop-manifest.yaml"
- type: "Local Folder"
path: "/home/k8s/educates/educates-workshops/crossplane-workshop/templates/crossplane-config"
layer: "addon"
addon_type: "system app"
k8s_version:
min: "1.18"
max: "1.32"
dependencies:
- name: "test"
layer: "addon"
type: "required"
namespace: "my-namespace"
#!/bin/bash
# Load YAML config if provided
CONFIG_FILE=""
if [[ "$1" == "--config" ]]; then
CONFIG_FILE="$2"
shift 2
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Config file $CONFIG_FILE not found!"
exit 1
fi
# Load the YAML config into variables using `yq`
CONFIG=$(yq eval '.' "$CONFIG_FILE")
fi
# Function to retrieve value from YAML config or use gum input if not provided
get_input() {
local yaml_key="$1"
local prompt="$2"
local placeholder="$3"
local password="$4"
if [[ -n "$CONFIG_FILE" ]]; then
local value=$(echo "$CONFIG" | yq eval "$yaml_key" -)
if [[ -n "$value" && "$value" != "null" ]]; then
echo "$value"
return
fi
fi
if [[ "$password" == "true" ]]; then
gum input --prompt "$prompt" --password
else
gum input --prompt "$prompt" --placeholder "$placeholder"
fi
}
check_tools() {
MISSING_TOOLS=()
REQUIRED_TOOLS=("jq" "helm" "oras" "gum" "yq")
for TOOL in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "$TOOL" &> /dev/null; then
MISSING_TOOLS+=("$TOOL")
fi
done
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
echo "The following tools are missing:"
for TOOL in "${MISSING_TOOLS[@]}"; do
echo "- $TOOL"
done
exit 1
fi
}
create_registry_layout() {
gum style --bold --foreground 6 "Registry Configuration"
REGISTRY_NAME=$(get_input ".registry.name" "Enter registry name: ")
REGISTRY_FQDN=$(get_input ".registry.fqdn" "Enter registry FQDN: ")
USERNAME=$(get_input ".registry.username" "Enter registry username: ")
PASSWORD=$(get_input ".registry.password" "Enter registry password: " "" true)
SUBPATH=$(get_input ".registry.subpath" "Enter registry sub-path/project name: ")
mkdir -p "$REGISTRY_NAME/packs"
cd "$REGISTRY_NAME" || exit 1
git init
echo ".registry-config" > .gitignore
cat <<EOF > ".registry-config"
{
"fqdn": "$REGISTRY_FQDN",
"subpath": "$SUBPATH",
"username": "$USERNAME",
"password": "$PASSWORD"
}
EOF
gum style --foreground 2 "Registry layout created successfully at packs/$REGISTRY_NAME."
}
select_or_add_helm_repo() {
gum style --bold --foreground 6 "Helm Repository Selection"
if [[ -n "$CONFIG_FILE" ]]; then
echo $CONFIG_FILE
REPO_NAME=$(get_input ".pack.repo.name" "Enter Helm repository name: ")
REPO_URL=$(get_input ".pack.repo.url" "Enter Helm repository URL: ")
else
if gum confirm "Is this an existing repository?"; then
EXISTING_REPOS=($(helm repo list -o json | jq -r '.[].name'))
if [ ${#EXISTING_REPOS[@]} -eq 0 ]; then
gum style --foreground 1 "No existing repositories found. You need to add a new one."
else
REPO_NAME=$(gum choose "${EXISTING_REPOS[@]}")
helm repo update "$REPO_NAME"
return
fi
fi
REPO_NAME=$(gum input --prompt "Enter new Helm repository name: ")
REPO_URL=$(gum input --prompt "Enter Helm repository URL: ")
fi
if helm repo list | grep -q "$REPO_NAME"; then
EXISTING_URL=$(helm repo list | grep "$REPO_NAME" | awk '{print $2}')
if [ "$EXISTING_URL" != "$REPO_URL" ]; then
echo "REPO NAME: $REPO_NAME"
echo "$EXISTING_URL != $REPO_URL"
gum style --foreground 1 "Repository name exists but with a different URL. Please choose a different name."
exit 1
fi
else
helm repo add "$REPO_NAME" "$REPO_URL" || exit 1
fi
}
create_manifest_based_pack() {
if [[ ! -d "packs" || ! -f ".registry-config" ]]; then
echo "Error: This command must be run from within a specific pack registry folder. One can be created with the \"$(basename "$0") create registry-layout\" command"
exit 1
fi
gum style --bold --foreground 6 "Manifest based Pack Creation"
PACK_NAME=$(get_input ".pack.name" "Enter pack name: ")
PACK_VERSION=$(get_input ".pack.version" "Enter pack version: ")
DISPLAY_NAME=$(get_input ".pack.display_name" "Enter display name (default: $PACK_NAME): " "$PACK_NAME")
mkdir -p "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests"
if [[ -n "$CONFIG_FILE" ]]; then
MANIFEST_SOURCES_LENGTH=$(echo "$CONFIG" | yq eval '.pack.manifest_sources | length' -)
for (( i=0; i<MANIFEST_SOURCES_LENGTH; i++ )); do
SOURCE_TYPE=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].type" -)
case $SOURCE_TYPE in
"HTTP URL")
URL=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].url" -)
FILENAME=$(basename "$URL")
curl -L "$URL" -o "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME" || { gum style --foreground 1 "Error downloading $URL"; exit 1; }
;;
"Local File")
FILE_PATH=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].path" -)
if [[ -f "$FILE_PATH" ]]; then
FILENAME=$(basename "$FILE_PATH")
cp "$FILE_PATH" "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME"
else
gum style --foreground 1 "Invalid file path: $FILE_PATH"
exit 1
fi
;;
"Local Folder")
FOLDER_PATH=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].path" -)
if [[ -d "$FOLDER_PATH" ]]; then
cp -r "$FOLDER_PATH"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
else
gum style --foreground 1 "Invalid folder path: $FOLDER_PATH"
exit 1
fi
;;
"Git Repository")
REPO_URL=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].url" -)
BRANCH=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].branch" -)
CONTENT_TYPE=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].content_type" -)
FILE_PATH=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].file_path" -)
SUB_FOLDER=$(echo "$CONFIG" | yq eval ".pack.manifest_sources[$i].sub_folder" -)
BASE_DIRECTORY=$(pwd)
TEMP_DIR=$(mktemp -d)
git clone "$REPO_URL" "$TEMP_DIR" || { gum style --foreground 1 "Error cloning repository"; exit 1; }
cd "$TEMP_DIR" || exit 1
git checkout "${BRANCH:-main}" || { gum style --foreground 1 "Error checking out branch"; exit 1; }
cd "$BASE_DIRECTORY" || exit 1
case $CONTENT_TYPE in
"Whole repository")
cp -r "$TEMP_DIR"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
;;
"Sub-folder")
if [[ -d "$TEMP_DIR/$SUB_FOLDER" ]]; then
cp -r "$TEMP_DIR/$SUB_FOLDER"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
else
gum style --foreground 1 "Invalid sub-folder path: $SUB_FOLDER"
exit 1
fi
;;
"Specific file")
if [[ -f "$TEMP_DIR/$FILE_PATH" ]]; then
FILENAME=$(basename "$FILE_PATH")
cp "$TEMP_DIR/$FILE_PATH" "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME"
else
gum style --foreground 1 "Invalid file path: $FILE_PATH"
exit 1
fi
;;
esac
rm -rf "$TEMP_DIR"
;;
*)
gum style --foreground 1 "Unknown manifest source type: $SOURCE_TYPE"
exit 1
;;
esac
done
else
while gum confirm "Do you want to add a manifest source?"; do
SOURCE_TYPE=$(gum choose "HTTP URL" "Local File" "Local Folder" "Git Repository")
case $SOURCE_TYPE in
"HTTP URL")
URL=$(gum input --prompt "Enter the HTTP URL to the manifest file: ")
FILENAME=$(basename "$URL")
curl -L "$URL" -o "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME" || { gum style --foreground 1 "Error downloading $URL"; exit 1; }
;;
"Local File")
FILE_PATH=$(gum input --prompt "Enter the full path to the local file: ")
if [[ -f "$FILE_PATH" ]]; then
FILENAME=$(basename "$FILE_PATH")
cp "$FILE_PATH" "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME"
else
gum style --foreground 1 "Invalid file path: $FILE_PATH"
exit 1
fi
;;
"Local Folder")
FOLDER_PATH=$(gum input --prompt "Enter the full path to the local folder: ")
if [[ -d "$FOLDER_PATH" ]]; then
cp -r "$FOLDER_PATH"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
else
gum style --foreground 1 "Invalid folder path: $FOLDER_PATH"
exit 1
fi
;;
"Git Repository")
REPO_URL=$(gum input --prompt "Enter the Git repository URL: ")
BASE_DIRECTORY=$(pwd)
TEMP_DIR=$(mktemp -d)
git clone "$REPO_URL" "$TEMP_DIR" || { gum style --foreground 1 "Error cloning repository"; exit 1; }
BRANCH=$(gum input --prompt "Enter the branch (default: main): " --placeholder "main")
cd "$TEMP_DIR"
git checkout "${BRANCH:-main}" || { gum style --foreground 1 "Error checking out branch"; exit 1; }
cd $BASE_DIRECTORY
CONTENT_TYPE=$(gum choose "Whole repository" "Sub-folder" "Specific file")
case $CONTENT_TYPE in
"Whole repository")
cp -r "$TEMP_DIR"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
;;
"Sub-folder")
SUB_FOLDER=$(gum input --prompt "Enter the path of the sub-folder: ")
if [[ -d "$TEMP_DIR/$SUB_FOLDER" ]]; then
pwd
cp -r "$TEMP_DIR/$SUB_FOLDER"/* "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/"
else
gum style --foreground 1 "Invalid sub-folder path: $SUB_FOLDER"
exit 1
fi
;;
"Specific file")
FILE_PATH=$(gum input --prompt "Enter the path of the file within the repository: ")
if [[ -f "$TEMP_DIR/$FILE_PATH" ]]; then
FILENAME=$(basename "$FILE_PATH")
ls -lah
pwd
cp "$TEMP_DIR/$FILE_PATH" "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/manifests/$FILENAME"
else
gum style --foreground 1 "Invalid file path: $FILE_PATH"
exit 1
fi
;;
esac
rm -rf "$TEMP_DIR"
;;
esac
done
fi
BASE_DIRECTORY=$(pwd)
cd "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME" || exit 1
MANIFEST_SOURCES=($(find "manifests" -type f \( -name "*.yaml" -o -name "*.yml" \)))
cd "$BASE_DIRECTORY" || exit 1
if [[ -n "$CONFIG_FILE" ]]; then
PACK_LAYER=$(get_input ".pack.layer" "Select pack layer: " "addon")
else
PACK_LAYER=$(gum choose "os" "k8s" "cni" "csi" "addon")
fi
if [ "$PACK_LAYER" == "addon" ]; then
if [[ -n "$CONFIG_FILE" ]]; then
ADDON_TYPE=$(get_input ".pack.addon_type" "Select addon type: ")
else
ADDON_TYPE=$(gum choose "logging" "monitoring" "load balancer" "authentication" "ingress" "security" "system app")
fi
fi
MIN_K8S_VERSION=$(get_input ".pack.k8s_version.min" "Enter minimum K8s version (or leave empty for none): ")
MAX_K8S_VERSION=$(get_input ".pack.k8s_version.max" "Enter maximum K8s version (or leave empty for none): ")
DEPENDENCIES=()
if [[ -n "$CONFIG_FILE" ]]; then
DEPENDENCIES_LENGTH=$(echo "$CONFIG" | yq eval '.pack.dependencies | length' -)
for (( i=0; i<DEPENDENCIES_LENGTH; i++ )); do
DEP_PACK_NAME=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].name" -)
DEP_LAYER=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].layer" -)
DEP_MIN_VERSION=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].min_version" -)
DEP_MAX_VERSION=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].max_version" -)
DEP_TYPE=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].type" -)
DEPENDENCIES+=("{\"packName\":\"$DEP_PACK_NAME\",\"layer\":\"$DEP_LAYER\",\"minVersion\":\"$DEP_MIN_VERSION\",\"maxVersion\":\"$DEP_MAX_VERSION\",\"type\":\"$DEP_TYPE\"}")
done
else
while gum confirm "Does the pack have constraints regarding other packs?"; do
DEP_PACK_NAME=$(gum input --prompt "Enter dependent pack name: ")
DEP_LAYER=$(gum input --prompt "Enter dependent pack layer: ")
DEP_MIN_VERSION=$(gum input --prompt "Enter dependent pack minVersion: ")
DEP_MAX_VERSION=$(gum input --prompt "Enter dependent pack maxVersion: ")
DEP_TYPE=$(gum choose "optional" "required" "notSupported")
DEPENDENCIES+=("{\"packName\":\"$DEP_PACK_NAME\",\"layer\":\"$DEP_LAYER\",\"minVersion\":\"$DEP_MIN_VERSION\",\"maxVersion\":\"$DEP_MAX_VERSION\",\"type\":\"$DEP_TYPE\"}")
done
fi
NAMESPACE=$(get_input ".pack.namespace" "Enter namespace for pack installation: ")
VALUES_FILE="packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/values.yaml"
cat <<EOF > "$VALUES_FILE"
pack:
namespace: $NAMESPACE
EOF
PACK_JSON_FILE="packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/pack.json"
jq -n --arg source "community" \
--arg contributor "TeraSky" \
--arg displayName "$DISPLAY_NAME" \
--arg eol " " \
--arg group " " \
--argjson kubeManifests "$(printf '%s\n' "${MANIFEST_SOURCES[@]}" | jq -R . | jq -s '.')" \
--arg layer "$PACK_LAYER" \
--arg name "$PACK_NAME" \
--arg version "$PACK_VERSION" \
'{
annotations: {
source: $source,
contributor: $contributor
},
ansibleRoles: [],
cloudTypes: ["all"],
displayName: $displayName,
eol: $eol,
group: $group,
kubeManifests: $kubeManifests,
layer: $layer,
name: $name,
version: $version
}' > "$PACK_JSON_FILE"
# Add "addonType" if it's an addon
if [ "$PACK_LAYER" == "addon" ]; then
jq --arg addonType "$ADDON_TYPE" '. + {addonType: $addonType}' "$PACK_JSON_FILE" > tmp.json && mv tmp.json "$PACK_JSON_FILE"
fi
# Add constraints if needed
if [ -n "$MIN_K8S_VERSION" ] || [ -n "$MAX_K8S_VERSION" ] || [ ${#DEPENDENCIES[@]} -ne 0 ]; then
constraints=$(jq -n '{dependencies: []}')
# Add Kubernetes constraint if versions are provided
if [ -n "$MIN_K8S_VERSION" ] || [ -n "$MAX_K8S_VERSION" ]; then
kubeConstraint=$(jq -n --arg minVersion "$MIN_K8S_VERSION" --arg maxVersion "$MAX_K8S_VERSION" '[{
packName: "kubernetes",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-eks",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-aks",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-gke",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-rke2",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-microk8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-konvoy",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-k3s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-k8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-rke2",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-microk8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
}]')
constraints=$(echo "$constraints" | jq --argjson kubeConstraint "$kubeConstraint" '.dependencies += $kubeConstraint')
fi
# Add additional dependencies
for DEP in "${DEPENDENCIES[@]}"; do
constraints=$(echo "$constraints" | jq --argjson dep "$DEP" '.dependencies += [$dep]')
done
jq --argjson constraints "$constraints" '. + {constraints: $constraints}' "$PACK_JSON_FILE" > tmp.json && mv tmp.json "$PACK_JSON_FILE"
fi
gum style --foreground 2 "Pack $PACK_NAME-$PACK_VERSION created successfully under packs directory."
}
create_helm_based_pack() {
if [[ ! -d "packs" || ! -f ".registry-config" ]]; then
echo "Error: This command must be run from within a specific pack registry folder. One can be created with the \"$(basename "$0") create registry-layout\" command"
exit 1
fi
gum style --bold --foreground 6 "Helm-based Pack Creation"
PACK_NAME=$(get_input ".pack.name" "Enter pack name: ")
PACK_VERSION=$(get_input ".pack.version" "Enter pack version: ")
DISPLAY_NAME=$(get_input ".pack.display_name" "Enter display name (default: $PACK_NAME): " "$PACK_NAME")
select_or_add_helm_repo
CHARTS=$(helm search repo "$REPO_NAME/" -o json | jq -r '.[].name')
if [[ -n "$CONFIG_FILE" ]]; then
SELECTED_CHARTS=($(echo "$CONFIG" | yq eval '.pack.charts[]' -))
else
gum style --bold --foreground 6 "Select Charts"
SELECTED_CHARTS=($(gum choose --no-limit "${CHARTS[@]}"))
fi
if [ ${#SELECTED_CHARTS[@]} -eq 0 ]; then
gum style --foreground 1 "No charts selected. Exiting."
exit 1
fi
mkdir -p "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/charts"
for CHART in "${SELECTED_CHARTS[@]}"; do
if [[ -n "$CONFIG_FILE" ]]; then
CHART_VERSION=$(get_input ".pack.chart_versions.$CHART" "Enter version for chart '$CHART': ")
else
VERSIONS=$(helm search repo "$CHART" -o json --versions | jq -r '.[].version')
CHART_VERSION=$(echo "$VERSIONS" | gum choose --header "Select version for chart '$CHART': ")
fi
helm pull "$CHART" --version "$CHART_VERSION" --destination "packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/charts"
done
PACK_LAYER=$(get_input ".pack.layer" "Select pack layer: " "addon")
if [ "$PACK_LAYER" == "addon" ]; then
if [[ -n "$CONFIG_FILE" ]]; then
ADDON_TYPE=$(get_input ".pack.addon_type" "Select addon type: ")
else
ADDON_TYPE=$(gum choose "logging" "monitoring" "load balancer" "authentication" "ingress" "security" "system app")
fi
fi
MIN_K8S_VERSION=$(get_input ".pack.k8s_version.min" "Enter minimum K8s version (or leave empty for none): ")
MAX_K8S_VERSION=$(get_input ".pack.k8s_version.max" "Enter maximum K8s version (or leave empty for none): ")
DEPENDENCIES=()
if [[ -n "$CONFIG_FILE" ]]; then
DEPENDENCIES_LENGTH=$(echo "$CONFIG" | yq eval '.pack.dependencies | length' -)
for (( i=0; i<DEPENDENCIES_LENGTH; i++ )); do
DEP_PACK_NAME=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].name" -)
DEP_LAYER=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].layer" -)
DEP_MIN_VERSION=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].min_version" -)
DEP_MAX_VERSION=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].max_version" -)
DEP_TYPE=$(echo "$CONFIG" | yq eval ".pack.dependencies[$i].type" -)
DEPENDENCIES+=("{\"packName\":\"$DEP_PACK_NAME\",\"layer\":\"$DEP_LAYER\",\"minVersion\":\"$DEP_MIN_VERSION\",\"maxVersion\":\"$DEP_MAX_VERSION\",\"type\":\"$DEP_TYPE\"}")
done
else
while gum confirm "Does the pack have constraints regarding other packs?"; do
DEP_PACK_NAME=$(gum input --prompt "Enter dependent pack name: ")
DEP_LAYER=$(gum input --prompt "Enter dependent pack layer: ")
DEP_MIN_VERSION=$(gum input --prompt "Enter dependent pack minVersion: ")
DEP_MAX_VERSION=$(gum input --prompt "Enter dependent pack maxVersion: ")
DEP_TYPE=$(gum choose "optional" "required" "notSupported")
DEPENDENCIES+=("{\"packName\":\"$DEP_PACK_NAME\",\"layer\":\"$DEP_LAYER\",\"minVersion\":\"$DEP_MIN_VERSION\",\"maxVersion\":\"$DEP_MAX_VERSION\",\"type\":\"$DEP_TYPE\"}")
done
fi
NAMESPACE=$(get_input ".pack.namespace" "Enter namespace for pack installation: ")
VALUES_FILE="packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/values.yaml"
cat <<EOF > "$VALUES_FILE"
pack:
namespace: $NAMESPACE
charts:
EOF
for CHART in "${SELECTED_CHARTS[@]}"; do
DEFAULT_VALUES=$(helm show values "$CHART")
CHART_SHORT_NAME="${CHART#*/}"
echo " $CHART_SHORT_NAME:" >> "$VALUES_FILE"
echo "$DEFAULT_VALUES" | sed 's/^/ /' >> "$VALUES_FILE"
done
PACK_JSON_FILE="packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/pack.json"
# Create base JSON structure
CHART_FILES=()
for CHART in ./packs/$PACK_NAME-$PACK_VERSION/$PACK_NAME/charts/*; do
CHART_FILE="charts/$(basename $CHART)"
CHART_FILES+=("$CHART_FILE")
done
jq -n --arg source "community" \
--arg contributor "TeraSky" \
--arg displayName "$DISPLAY_NAME" \
--arg eol " " \
--arg group " " \
--argjson charts "$(printf '%s\n' "${CHART_FILES[@]}" | jq -R . | jq -s '.')" \
--arg layer "$PACK_LAYER" \
--arg name "$PACK_NAME" \
--arg version "$PACK_VERSION" \
'{
annotations: {
source: $source,
contributor: $contributor
},
ansibleRoles: [],
cloudTypes: ["all"],
displayName: $displayName,
eol: $eol,
group: $group,
kubeManifests: [],
charts: $charts,
layer: $layer,
name: $name,
version: $version
}' > "$PACK_JSON_FILE"
# Add "addonType" if it's an addon
if [ "$PACK_LAYER" == "addon" ]; then
jq --arg addonType "$ADDON_TYPE" '. + {addonType: $addonType}' "$PACK_JSON_FILE" > tmp.json && mv tmp.json "$PACK_JSON_FILE"
fi
# Add constraints if needed
if [ -n "$MIN_K8S_VERSION" ] || [ -n "$MAX_K8S_VERSION" ] || [ ${#DEPENDENCIES[@]} -ne 0 ]; then
constraints=$(jq -n '{dependencies: []}')
# Add Kubernetes constraint if versions are provided
if [ -n "$MIN_K8S_VERSION" ] || [ -n "$MAX_K8S_VERSION" ]; then
kubeConstraint=$(jq -n --arg minVersion "$MIN_K8S_VERSION" --arg maxVersion "$MAX_K8S_VERSION" '[{
packName: "kubernetes",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-eks",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-aks",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-gke",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-rke2",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-microk8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "kubernetes-konvoy",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-k3s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-k8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-rke2",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
},{
packName: "edge-microk8s",
layer: "k8s",
minVersion: $minVersion,
maxVersion: $maxVersion,
type: "optional"
}]')
constraints=$(echo "$constraints" | jq --argjson kubeConstraint "$kubeConstraint" '.dependencies += $kubeConstraint')
fi
# Add additional dependencies
for DEP in "${DEPENDENCIES[@]}"; do
constraints=$(echo "$constraints" | jq --argjson dep "$DEP" '.dependencies += [$dep]')
done
jq --argjson constraints "$constraints" '. + {constraints: $constraints}' "$PACK_JSON_FILE" > tmp.json && mv tmp.json "$PACK_JSON_FILE"
fi
gum style --foreground 2 "Pack $PACK_NAME-$PACK_VERSION created successfully under packs directory."
}
push_pack() {
REGISTRY_CONFIG=".registry-config"
if [ ! -f "$REGISTRY_CONFIG" ]; then
gum style --foreground 1 "Registry configuration file not found!"
exit 1
fi
PACKS_DIR="packs"
PACKS=($(find packs -maxdepth 1 -mindepth 1 -type d -exec basename {} \;))
if [[ -n "$CONFIG_FILE" ]]; then
SELECTED_PACK=$(get_input ".pack.name" "Select pack to push: ")
else
SELECTED_PACK=$(gum choose "${PACKS[@]}")
fi
REGISTRY_FQDN=$(jq -r '.fqdn' "$REGISTRY_CONFIG")
REGISTRY_SUBPATH=$(jq -r '.subpath' "$REGISTRY_CONFIG")
USERNAME=$(jq -r '.username' "$REGISTRY_CONFIG")
PASSWORD=$(jq -r '.password' "$REGISTRY_CONFIG")
oras login -u "$USERNAME" -p "$PASSWORD" "$REGISTRY_FQDN" --insecure
PACK_ARCHIVE="${SELECTED_PACK##*/}.tar.gz"
PACK_NAME=$(echo "${SELECTED_PACK%-*}" | tr '[:upper:]' '[:lower:]')
PACK_VERSION="${SELECTED_PACK##*-}"
cd "packs/$SELECTED_PACK" || exit 1
tar -czvf "$PACK_ARCHIVE" "$PACK_NAME"
oras push "$REGISTRY_FQDN/$REGISTRY_SUBPATH/spectro-packs/archive/$PACK_NAME:$PACK_VERSION" "$PACK_ARCHIVE"
cd ../../
mkdir -p pack-bundles
mv "packs/$SELECTED_PACK/$PACK_ARCHIVE" pack-bundles/
gum style --foreground 2 "Pack pushed and archived successfully."
}
install_dependencies() {
TOOLS=("jq" "helm" "oras" "gum" "yq")
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check if all required tools are installed
missing_tools=()
for tool in "${TOOLS[@]}"; do
if ! command_exists "$tool"; then
missing_tools+=("$tool")
fi
done
# If all tools are installed, exit
if [ ${#missing_tools[@]} -eq 0 ]; then
echo "All required tools are already installed."
exit 0
fi
# Check if Homebrew is installed
if ! command_exists "brew"; then
echo "Error: The following tools are missing: ${missing_tools[*]}"
echo "Homebrew is required to install these tools. Please install Homebrew first."
exit 1
fi
# Install missing tools using Homebrew
for tool in "${missing_tools[@]}"; do
case $tool in
jq)
echo "Installing jq..."
brew install jq
;;
helm)
echo "Installing helm..."
brew install helm
;;
oras)
echo "Installing oras..."
brew install oras
;;
yq)
echo "Installing YQ..."
brew install yq
;;
gum)
echo "Installing gum 1.13.0..."
wget https://raw.githubusercontent.com/charmbracelet/homebrew-tap/a8616717837d78b31484464cde92170b5570e20e/gum.rb
brew install gum.rb
rm -f gum.rb
;;
esac
done
echo "Installation complete for missing tools."
}
# Main script logic
case "$1" in
create)
case "$2" in
registry-layout)
check_tools
create_registry_layout
;;
pack)
check_tools
if [[ -n "$CONFIG_FILE" ]]; then
PACK_TYPE=$(get_input ".pack.type" "Which type of pack do you want to create?" "Helm" "YAML Manifests")
else
PACK_TYPE=$(gum choose --header "Which type of pack do you want to create?" "Helm" "YAML Manifests")
fi
case "$PACK_TYPE" in
"Helm")
create_helm_based_pack
;;
"YAML Manifests")
create_manifest_based_pack
;;
*)
gum style --foreground 1 "Unknown pack type. Available: Helm, YAML Manifests"
exit 1
;;
esac
;;
*)
gum style --foreground 1 "Unknown create subcommand. Available: registry-layout, pack"
exit 1
;;
esac
;;
push)
case "$2" in
pack)
check_tools
push_pack
;;
*)
gum style --foreground 1 "Unknown push subcommand. Available: pack"
exit 1
;;
esac
;;
install-dependencies)
install_dependencies
;;
*)
gum style --foreground 1 "Unknown command. Available: create, push, install-dependencies"
exit 1
;;
esac
pack:
name: "demo-1.0.0"
registry:
name: "my-harbor" # Local name for the registry
fqdn: "harbor.vrabbi.cloud"
username: "myuser"
password: "mypassword"
subpath: "packs" # In harbor this refers to a project
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment