230 lines
8.1 KiB
Bash
Executable File
230 lines
8.1 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
# Universal script for setting GitLab CI/CD variables via API
|
|
|
|
# Print help and exit if --help or -h is passed, or no arguments
|
|
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" || $# -eq 0 ]]; then
|
|
cat <<EOF
|
|
set-gitlab-vars — universal script for setting GitLab CI/CD variables via API
|
|
|
|
Usage:
|
|
1. Bulk from YAML file:
|
|
export GITLAB_URL="https://gitlab.example.com"
|
|
export PROJECT=4 # or export PROJECT=owner_or_group/repo
|
|
export GITLAB_TOKEN=...
|
|
set-gitlab-vars path/to/file.yml
|
|
|
|
2. Single variable via arguments:
|
|
set-gitlab-vars VAR_NAME VAR_VALUE [ENV_SCOPE] [VAR_TYPE] [PROTECTED] [RAW] [MASKED] [MASKED_AND_HIDDEN]
|
|
# Example:
|
|
set-gitlab-vars MY_SECRET "supervalue" "Production" "file" true true false false
|
|
|
|
3. Help:
|
|
set-gitlab-vars --help
|
|
|
|
YAML file structure:
|
|
VAR_NAME:
|
|
value: "..." # required
|
|
environment_scope: "Dev" # optional
|
|
variable_type: "env_var" # or "file", optional (default: env_var)
|
|
protected: false # optional (default: false)
|
|
raw: true # optional (default: true)
|
|
masked: false # optional (default: false)
|
|
masked_and_hidden: false # optional (default: false)
|
|
description: "..." # optional (default: null)
|
|
|
|
Required environment variables:
|
|
GITLAB_URL — GitLab address
|
|
PROJECT — project ID or path (e.g. 4 or owner_or_group/repo)
|
|
GITLAB_TOKEN — token with api scope
|
|
|
|
Notes:
|
|
- All boolean options (protected, raw, masked, masked_and_hidden) default to false except raw (default: true).
|
|
- description is only supported when using YAML input.
|
|
- For file variables, multiline and special characters are supported.
|
|
- For env_var variables, multiline values are supported via form-data.
|
|
- All options are supported both in YAML and via CLI (except description, which is YAML only).
|
|
EOF
|
|
exit 0
|
|
fi
|
|
|
|
# Error/info helpers
|
|
err() { echo "[ERROR] $*" >&2; }
|
|
info() { echo "[INFO] $*"; }
|
|
|
|
# Check required environment variables
|
|
if [[ -z "${GITLAB_URL:-}" ]]; then
|
|
err "GITLAB_URL is not set. Export GitLab address, e.g.: export GITLAB_URL=\"https://gitlab.example.com\""
|
|
exit 1
|
|
fi
|
|
if [[ -z "${PROJECT:-}" ]]; then
|
|
err "PROJECT is not set. Export project ID or path, e.g.: export PROJECT=4 or export PROJECT=owner_or_group/repo"
|
|
exit 1
|
|
fi
|
|
if [[ -z "${GITLAB_TOKEN:-}" ]]; then
|
|
err "GITLAB_TOKEN is not set. Export access token, e.g.: export GITLAB_TOKEN=\"<your_token>\""
|
|
exit 1
|
|
fi
|
|
|
|
urlencode() {
|
|
local LANG=C
|
|
local length="${#1}"
|
|
for (( i = 0; i < length; i++ )); do
|
|
local c="${1:i:1}"
|
|
case $c in
|
|
[a-zA-Z0-9.~_-]) printf "$c" ;;
|
|
*) printf '%%%02X' "'${c}" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
PROJECT_ENC="$(urlencode "$PROJECT")"
|
|
|
|
CURL_BASE=(curl -sS --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}")
|
|
|
|
# JSON escaping for file variables without python3
|
|
escape_json() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}" # \
|
|
s="${s//\"/\\\"}" # "
|
|
s="${s//$'\n'/\\n}" # newlines
|
|
s="${s//$'\t'/\\t}" # tabs
|
|
echo -n "$s"
|
|
}
|
|
|
|
set_var() {
|
|
VAR_NAME="$1"
|
|
VAR_VALUE="$2"
|
|
ENVIRONMENT_SCOPE="${3:-}"
|
|
VARIABLE_TYPE="${4:-env_var}"
|
|
PROTECTED="${5:-false}"
|
|
RAW="${6:-true}"
|
|
MASKED="${7:-false}"
|
|
MASKED_AND_HIDDEN="${8:-false}"
|
|
DESCRIPTION="${9:-null}"
|
|
info "Setting variable: $VAR_NAME, scope: $ENVIRONMENT_SCOPE, type: $VARIABLE_TYPE, protected: $PROTECTED, raw: $RAW, masked: $MASKED, masked_and_hidden: $MASKED_AND_HIDDEN, description: $DESCRIPTION"
|
|
|
|
# Prepare request body and headers
|
|
if [[ "$VARIABLE_TYPE" == "file" ]]; then
|
|
# For file variables, use JSON for correct escaping (no python3)
|
|
ESCAPED_VALUE=$(escape_json "$VAR_VALUE")
|
|
JSON_BODY="{\"key\":\"$VAR_NAME\",\"value\":\"$ESCAPED_VALUE\",\"variable_type\":\"$VARIABLE_TYPE\",\"protected\":$PROTECTED,\"raw\":$RAW,\"masked\":$MASKED,\"masked_and_hidden\":$MASKED_AND_HIDDEN"
|
|
if [[ "$DESCRIPTION" != "null" ]]; then
|
|
JSON_BODY+=" ,\"description\":\"$DESCRIPTION\""
|
|
fi
|
|
if [[ -n "$ENVIRONMENT_SCOPE" ]]; then
|
|
JSON_BODY+=" ,\"environment_scope\":\"$ENVIRONMENT_SCOPE\""
|
|
fi
|
|
JSON_BODY+="}"
|
|
REQUEST_ARGS=(--header "Content-Type: application/json" --data "$JSON_BODY")
|
|
else
|
|
# For env_var, use form-data to allow multiline
|
|
REQUEST_ARGS=(
|
|
--form "key=$VAR_NAME"
|
|
--form "value=$VAR_VALUE"
|
|
--form "variable_type=$VARIABLE_TYPE"
|
|
--form "protected=$PROTECTED"
|
|
--form "raw=$RAW"
|
|
--form "masked=$MASKED"
|
|
--form "masked_and_hidden=$MASKED_AND_HIDDEN"
|
|
)
|
|
if [[ "$DESCRIPTION" != "null" ]]; then
|
|
REQUEST_ARGS+=(--form "description=$DESCRIPTION")
|
|
fi
|
|
if [[ -n "$ENVIRONMENT_SCOPE" ]]; then
|
|
REQUEST_ARGS+=(--form "environment_scope=$ENVIRONMENT_SCOPE")
|
|
fi
|
|
fi
|
|
|
|
# Try to create variable
|
|
RESPONSE_BODY=$(mktemp)
|
|
RESPONSE_CODE=$("${CURL_BASE[@]}" -o "$RESPONSE_BODY" -w "%{http_code}" \
|
|
--request POST "$GITLAB_URL/api/v4/projects/$PROJECT_ENC/variables" \
|
|
"${REQUEST_ARGS[@]}")
|
|
if [[ "$RESPONSE_CODE" == "201" ]]; then
|
|
info "Variable $VAR_NAME created successfully."
|
|
rm -f "$RESPONSE_BODY"
|
|
return 0
|
|
elif [[ "$RESPONSE_CODE" == "409" || "$RESPONSE_CODE" == "400" && $(grep -q 'has already been taken' "$RESPONSE_BODY" && echo 1) == 1 ]]; then
|
|
info "Variable already exists, trying to update..."
|
|
# Update variable
|
|
UPDATE_URL="$GITLAB_URL/api/v4/projects/$PROJECT_ENC/variables/$VAR_NAME"
|
|
if [[ -n "$ENVIRONMENT_SCOPE" ]]; then
|
|
UPDATE_URL+="?filter%5Benvironment_scope%5D=$ENVIRONMENT_SCOPE"
|
|
fi
|
|
UPDATE_BODY=$(mktemp)
|
|
if [[ "$VARIABLE_TYPE" == "file" ]]; then
|
|
UPDATE_ARGS=(--header "Content-Type: application/json" --data "$JSON_BODY")
|
|
else
|
|
UPDATE_ARGS=(
|
|
--form "key=$VAR_NAME"
|
|
--form "value=$VAR_VALUE"
|
|
--form "variable_type=$VARIABLE_TYPE"
|
|
--form "protected=$PROTECTED"
|
|
--form "raw=true"
|
|
)
|
|
if [[ -n "$ENVIRONMENT_SCOPE" ]]; then
|
|
UPDATE_ARGS+=(--form "environment_scope=$ENVIRONMENT_SCOPE")
|
|
fi
|
|
fi
|
|
RESPONSE2=$("${CURL_BASE[@]}" -o "$UPDATE_BODY" -w "%{http_code}" \
|
|
--request PUT "$UPDATE_URL" "${UPDATE_ARGS[@]}")
|
|
if [[ "$RESPONSE2" == "200" ]]; then
|
|
info "Variable $VAR_NAME updated."
|
|
rm -f "$RESPONSE_BODY" "$UPDATE_BODY"
|
|
return 0
|
|
else
|
|
err "Failed to update variable $VAR_NAME (HTTP $RESPONSE2). GitLab API response: $(cat "$UPDATE_BODY")"
|
|
rm -f "$RESPONSE_BODY" "$UPDATE_BODY"
|
|
return 1
|
|
fi
|
|
elif [[ "$RESPONSE_CODE" == "400" ]]; then
|
|
err "Failed to create variable $VAR_NAME (HTTP 400)."
|
|
err "GitLab API response: $(cat "$RESPONSE_BODY")"
|
|
rm -f "$RESPONSE_BODY"
|
|
return 1
|
|
else
|
|
err "Failed to create variable $VAR_NAME (HTTP $RESPONSE_CODE)"
|
|
rm -f "$RESPONSE_BODY"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# If a yaml file is passed as an argument, load variables from the file
|
|
if [[ -n "${1:-}" && -f "$1" ]]; then
|
|
if command -v yq >/dev/null 2>&1; then
|
|
VARS_FILE="$1"
|
|
VAR_NAMES=( $(yq 'keys | .[]' "$VARS_FILE") )
|
|
for VAR in "${VAR_NAMES[@]}"; do
|
|
[[ "$VAR" == "\$schema" ]] && continue
|
|
VALUE=$(yq -r ".${VAR}.value" "$VARS_FILE")
|
|
ENV_SCOPE=$(yq -r ".${VAR}.environment_scope // \"\"" "$VARS_FILE")
|
|
VAR_TYPE=$(yq -r ".${VAR}.variable_type // \"env_var\"" "$VARS_FILE")
|
|
PROTECTED=$(yq -r ".${VAR}.protected // false" "$VARS_FILE")
|
|
RAW=$(yq -r ".${VAR}.raw // true" "$VARS_FILE")
|
|
MASKED=$(yq -r ".${VAR}.masked // false" "$VARS_FILE")
|
|
MASKED_AND_HIDDEN=$(yq -r ".${VAR}.masked_and_hidden // false" "$VARS_FILE")
|
|
DESCRIPTION=$(yq -r ".${VAR}.description // null" "$VARS_FILE")
|
|
set_var "$VAR" "$VALUE" "$ENV_SCOPE" "$VAR_TYPE" "$PROTECTED" "$RAW" "$MASKED" "$MASKED_AND_HIDDEN" "$DESCRIPTION"
|
|
done
|
|
exit 0
|
|
else
|
|
err "yq is required to work with yaml file (https://mikefarah.gitbook.io/yq/)"
|
|
exit 2
|
|
fi
|
|
fi
|
|
|
|
# If 2-5 positional arguments are passed, set a single variable
|
|
if [[ $# -ge 2 && $# -le 5 ]]; then
|
|
VAR_NAME="$1"
|
|
VAR_VALUE="$2"
|
|
ENV_SCOPE="${3:-}"
|
|
VAR_TYPE="${4:-env_var}"
|
|
PROTECTED="${5:-false}"
|
|
set_var "$VAR_NAME" "$VAR_VALUE" "$ENV_SCOPE" "$VAR_TYPE" "$PROTECTED"
|
|
exit $?
|
|
fi
|
|
|
|
err "Invalid arguments. Run with --help for usage."
|
|
exit 1
|