Skip to content

Instantly share code, notes, and snippets.

@gustavovalverde
Last active January 22, 2025 22:45
Show Gist options
  • Save gustavovalverde/b0e566c2da331ae51116db9f74d30583 to your computer and use it in GitHub Desktop.
Save gustavovalverde/b0e566c2da331ae51116db9f74d30583 to your computer and use it in GitHub Desktop.
A bash script to handle GitHub environment variables, mainly to create new environments, and copy variables values from one environment to another
#!/bin/bash
# MIT License
# Copyright (c) 2024 Gustavo A. Valverde
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -e
REPO=""
log() {
echo "[INFO] $1"
}
error() {
echo "[ERROR] $1" >&2
}
# Parse .env file
parse_env_file() {
ENV_FILE=$1
VARIABLES=()
while IFS= read -r line || [ -n "$line" ]; do
[[ "$line" =~ ^#.*$ ]] && continue
[[ ! "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*=.*$ ]] && continue
VARIABLES+=("$line")
done < "$ENV_FILE"
}
# Create environment if it doesn't exist
create_environment() {
ENV=$1
if gh api "repos/$REPO/environments/$ENV" --method PUT > /dev/null 2>&1; then
log "Created environment: $ENV"
else
log "Environment $ENV already exists"
fi
}
# Set variable in environment
set_variable() {
ENV=$1
KEY=$2
VALUE=$3
if gh api -X POST "repos/$REPO/environments/$ENV/variables" -f "name=$KEY" -f "value=$(printf '%q' "$VALUE")" > /dev/null 2>&1; then
log "Set variable $KEY in environment $ENV"
else
log "Failed to set variable $KEY in environment $ENV"
fi
}
# Add variables to environment
add_variables_to_environment() {
ENV=$1
for VAR in "${VARIABLES[@]}"; do
IFS='=' read -r KEY VALUE <<< "$VAR"
set_variable "$ENV" "$KEY" "$VALUE"
done
}
# Create environments with variables
create_environments() {
ENVIRONMENTS=("${@:2}")
for ENV in "${ENVIRONMENTS[@]}"; do
create_environment "$ENV"
add_variables_to_environment "$ENV"
done
}
# Fetch variables from the source environment using GitHub API
fetch_variables_from_source() {
SOURCE_ENV=$1
VARIABLES=$(gh api "repos/$REPO/environments/$SOURCE_ENV/variables" --jq '.variables | map({key: .name, value: .value})')
}
# Fetch variables from a target environment using GitHub API
fetch_variables_from_target() {
TARGET_ENV=$1
EXISTING_VARIABLES=$(gh api "repos/$REPO/environments/$TARGET_ENV/variables" --jq '[.variables[] | {(.name): true}] | add')
}
# Check if a variable exists in the target environment
variable_exists_in_target() {
KEY=$1
echo "$EXISTING_VARIABLES" | jq -e "has(\"$KEY\")" > /dev/null 2>&1
}
# Transfer variables from one environment to others
transfer_variables() {
SOURCE_ENV=$1
TARGET_ENVS=("${@:2}")
fetch_variables_from_source "$SOURCE_ENV"
for TARGET_ENV in "${TARGET_ENVS[@]}"; do
create_environment "$TARGET_ENV"
fetch_variables_from_target "$TARGET_ENV"
echo "$VARIABLES" | jq -c '.[]' | while IFS= read -r var; do
KEY=$(echo "$var" | jq -r '.key')
VALUE=$(echo "$var" | jq -r '.value')
if variable_exists_in_target "$KEY"; then
if [[ "$OVERRIDE" = true ]]; then
log "Overriding variable $KEY in environment $TARGET_ENV"
set_variable "$TARGET_ENV" "$KEY" "$VALUE"
else
log "Variable $KEY already exists in environment $TARGET_ENV and will not be overwritten"
fi
else
log "Setting new variable $KEY in environment $TARGET_ENV"
set_variable "$TARGET_ENV" "$KEY" "$VALUE"
fi
done
done
}
# Override variables in environments
override_variables() {
ENVIRONMENTS=("${@:2}")
for ENV in "${ENVIRONMENTS[@]}"; do
create_environment "$ENV"
for VAR in "${VARIABLES[@]}"; do
IFS='=' read -r KEY VALUE <<< "$VAR"
set_variable "$ENV" "$KEY" "$VALUE"
done
done
}
# Main script
ENV_FILE=""
CREATE_ENVIRONMENTS=()
COPY_ENV=""
TARGET_ENVIRONMENTS=()
OVERRIDE=false
while [[ "$#" -gt 0 ]]; do
case $1 in
--repo)
REPO="$2"
shift 2
;;
--env)
ENV_FILE="$2"
shift 2
;;
--create)
IFS=',' read -r -a CREATE_ENVIRONMENTS <<< "$2"
shift 2
;;
--copy)
COPY_ENV="$2"
IFS=',' read -r -a TARGET_ENVIRONMENTS <<< "$3"
shift 3
;;
--override)
OVERRIDE=true
shift
;;
*)
error "Unknown parameter passed: $1"
exit 1
;;
esac
done
if [[ -z "$REPO" ]]; then
error "--repo is required"
exit 1
fi
if [[ -z "$ENV_FILE" && ${#CREATE_ENVIRONMENTS[@]} -ne 0 ]]; then
error "--env file is required for creating environments"
exit 1
fi
if [[ -n "$ENV_FILE" && ! -f "$ENV_FILE" ]]; then
error "The specified env file does not exist"
exit 1
fi
if [[ -n "$ENV_FILE" ]]; then
parse_env_file "$ENV_FILE"
fi
if [[ ${#CREATE_ENVIRONMENTS[@]} -ne 0 ]]; then
log "Creating environments: ${CREATE_ENVIRONMENTS[*]}"
create_environments "${CREATE_ENVIRONMENTS[@]}"
fi
if [[ -n "$COPY_ENV" && ${#TARGET_ENVIRONMENTS[@]} -ne 0 ]]; then
log "Copying variables from $COPY_ENV to ${TARGET_ENVIRONMENTS[*]}"
transfer_variables "$COPY_ENV" "${TARGET_ENVIRONMENTS[@]}"
fi
if [[ "$OVERRIDE" = true && ${#TARGET_ENVIRONMENTS[@]} -ne 0 ]]; then
log "Overriding variables in environments: ${TARGET_ENVIRONMENTS[*]}"
override_variables "${TARGET_ENVIRONMENTS[@]}"
fi
@excalq
Copy link

excalq commented Sep 5, 2024

The gh CLI now pretty-prints JSON when using --jq, breaking your script. To fix it, add this near the top:

export NO_COLOR=true
export GH_FORCE_TTY=false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment