#!/usr/bin/env bash # DEFAULT CONFIG OVH_CONSUMER_KEY="" OVH_APP_KEY="" OVH_APP_SECRET="" readonly CONSUMER_KEY_FILE=".ovhConsumerKey" readonly OVH_APPLICATION_FILE=".ovhApplication" readonly LIBS="libs" readonly TARGETS=(CA EU US) declare -A API_URLS API_URLS[CA]="https://ca.api.ovh.com/1.0" API_URLS[EU]="https://api.ovh.com/1.0" API_URLS[US]="https://api.ovhcloud.com/1.0" declare -A API_CREATE_APP_URLS API_CREATE_APP_URLS[CA]="https://ca.api.ovh.com/createApp/" API_CREATE_APP_URLS[EU]="https://api.ovh.com/createApp/" API_CREATE_APP_URLS[US]="https://api.ovhcloud.com/createApp/" readonly API_URLS readonly API_CREATE_APP_URLS ## https://gist.github.com/TheMengzor/968e5ea87e99d9c41782 # resolve $SOURCE until the file is no longer a symlink SOURCE="${BASH_SOURCE[0]}" while [[ -h "${SOURCE}" ]] do DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" SOURCE="$(readlink "${SOURCE}")" # if $SOURCE was a relative symlink, # we need to resolve it relative to the path where the symlink file was located [[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" done BASE_PATH=$( cd -P "$( dirname "${SOURCE}" )" && pwd ) readonly LEGACY_PROFILES_PATH="${BASE_PATH}/profile" readonly PROFILES_PATH="${HOME}/.ovh-api-bash-client/profile" HELP_CMD="$0" _echoWarning() { echo >&2 "[WARNING] $*" } # join alements of an array with a separator (single char) # usage: # _arrayJoin "|" "${my_array[@]}" # _arrayJoin() { local IFS="$1" shift echo "$*" } _StringToLower() { echo "$1" | tr '[:upper:]' '[:lower:]' } _StringToUpper() { echo "$1" | tr '[:lower:]' '[:upper:]' } # verify if an array contains an item # _in_array "wanted" "${array[@]}" # _in_array "wanted_key" "${!array[@]}" _in_array() { local item wanted wanted="$1" shift for item; do [[ "${item}" == "${wanted}" ]] && return 0 done return 1 } isTargetValid() { if ! _in_array "${TARGET}" "${TARGETS[@]}"; then help "'${TARGET}' is not a valid target, accepted values are: ${TARGETS[*]}" exit 1 fi } createApp() { local answer echo "For which OVH API do you want to create a new API Application? ($( _arrayJoin "|" "${TARGETS[@]}"))" while [[ -z "${answer}" ]] do read -r answer done TARGET=$( _StringToUpper "${answer}" ) isTargetValid echo echo -e "In order to create an API Application, please visit the link below:\\n${API_CREATE_APP_URLS[${TARGET}]}" echo echo "Once your application is created, we will configure this script for this application" echo -n "Enter the Application Key: " read -r OVH_APP_KEY echo -n "Enter the Application Secret: " read -r OVH_APP_SECRET echo "OK!" echo "These informations will be stored in the following file: ${CURRENT_PATH}/${OVH_APPLICATION_FILE}_${TARGET}" echo -e "${OVH_APP_KEY}\\n${OVH_APP_SECRET}" > "${CURRENT_PATH}/${OVH_APPLICATION_FILE}_${TARGET}" echo echo "Do you also need to create a consumer key? (y/n)" read -r answer if [[ -n "${answer}" ]] && [[ "$( _StringToLower "${answer}")" == "y" ]]; then createConsumerKey else echo -e "OK, no consumer key created for now.\\nYou will be able to initalize the consumer key later calling:\\n${HELP_CMD} --init" fi } createConsumerKey() { local answer # ensure an OVH App key is set initApplication hasOvhAppKey || exit 1 # condition keeped for retro-compatibility, to always allow post accessRules from --data if [[ -z "${POST_DATA}" ]]; then buildAccessRules fi answer=$(requestNoAuth "POST" "/auth/credential") getJSONFieldString "${answer}" 'consumerKey' > "${CURRENT_PATH}/${CONSUMER_KEY_FILE}_${TARGET}" echo "In order to validate the generated consumerKey, visit the validation url at:" getJSONFieldString "${answer}" 'validationUrl' } initConsumerKey() { if cat "${CURRENT_PATH}/${CONSUMER_KEY_FILE}_${TARGET}" &> /dev/null; then OVH_CONSUMER_KEY="$(cat "${CURRENT_PATH}/${CONSUMER_KEY_FILE}_${TARGET}")" fi } initApplication() { if cat "${CURRENT_PATH}/${OVH_APPLICATION_FILE}_${TARGET}" &> /dev/null; then OVH_APP_KEY=$(sed -n 1p "${CURRENT_PATH}/${OVH_APPLICATION_FILE}_${TARGET}") OVH_APP_SECRET=$(sed -n 2p "${CURRENT_PATH}/${OVH_APPLICATION_FILE}_${TARGET}") fi } updateTime() { # use OVH API's timestamp instead of user's one to bypass misconfigured host. curl -s "${API_URL}/auth/time" } # usage: # updateSignData "method" "url" "post_data" "timestamp" # return: print signature updateSignData() { local sig_data local method=$1 local url=$2 local post_data=$3 local timestamp=$4 sig_data="${OVH_APP_SECRET}+${OVH_CONSUMER_KEY}+${method}+${API_URL}${url}+${post_data}+${timestamp}" echo "\$1\$$(echo -n "${sig_data}" | sha1sum - | cut -d' ' -f1)" } help() { # print error message if set [[ -n "$1" ]] && echo -e "Error: $1\\n" cat < : the API URL to call, for example /domains (default is /me) --method : the HTTP method to use, for example POST (default is GET) --data : the data body to send with the request --target : the target API [$( _arrayJoin "|" "${TARGETS[@]}")] (default is EU) --init : to initialize the consumer key, and manage custom access rules file --initApp : to initialize the API application --list-profile : list available profiles in ~/.ovh-api-bash-client/profile directory --profile * default : from ~/.ovh-api-bash-client/profile directory * : from ~/.ovh-api-bash-client/profile/ directory EOF } buildAccessRules() { local access_rules_file="${CURRENT_PATH}/access.rules" local method path local json_rules local answer if [[ ! -f "${access_rules_file}" ]]; then echo "${access_rules_file} missing, created full access rules" echo -e "GET /*\\nPUT /*\\nPOST /*\\nDELETE /*" > "${CURRENT_PATH}/access.rules" fi echo -e "Current rules for that profile\\n" cat "${access_rules_file}" echo -e "\\nDo you need to customize this rules ?" read -n1 -r -p "(y/n)> " answer echo -e "\\n" case ${answer} in [Yy]) echo "Operation canceled, please edit ${access_rules_file}"; exit;; [Nn]) echo "Now generating POST JSON Data for accessRules";; *) echo "bad choice"; exit 1;; esac while read -r method path; do if [[ -n "${method}" ]] && [[ -n "${path}" ]]; then json_rules+='{ "method": "'${method}'", "path": "'${path}'"},' fi done < "${access_rules_file}" json_rules=${json_rules::-1} if [[ -z "${json_rules}" ]]; then echoWarning "no rule defined, please verify your file '${access_rules_file}'" exit 1 fi POST_DATA='{ "accessRules": [ '${json_rules}' ] }' } parseArguments() { # an action launched out of this function INIT_KEY_ACTION= while [[ $# -gt 0 ]] do case $1 in --data) shift POST_DATA=$1 ;; --init) INIT_KEY_ACTION="ConsumerKey" ;; --initApp) INIT_KEY_ACTION="AppKey" ;; --method) shift METHOD=$1 ;; --url) shift URL=$1 ;; --target) shift TARGET=$1 isTargetValid ;; --profile) shift PROFILE=$1 ;; --list-profile) listProfile exit 0 ;; --help|-h) help exit 0 ;; *) help "Unknow parameter $1" exit 0 ;; esac shift done } # usage: # requestNoAuth "method" "url" requestNoAuth() { local method=$1 local url=$2 local timestamp timestamp=$(updateTime) curl -s -X "${method}" \ --header 'Content-Type:application/json;charset=utf-8' \ --header "X-Ovh-Application:${OVH_APP_KEY}" \ --header "X-Ovh-Timestamp:${timestamp}" \ --data "${POST_DATA}" \ "${API_URL}${url}" } request() { local response response_status response_content sig timestamp timestamp=$(updateTime) sig=$(updateSignData "${METHOD}" "${URL}" "${POST_DATA}" "${timestamp}") response=$(curl -s -w '\n%{http_code}\n' -X "${METHOD}" \ --header 'Content-Type:application/json;charset=utf-8' \ --header "X-Ovh-Application:${OVH_APP_KEY}" \ --header "X-Ovh-Timestamp:${timestamp}" \ --header "X-Ovh-Signature:${sig}" \ --header "X-Ovh-Consumer:${OVH_CONSUMER_KEY}" \ --data "${POST_DATA}" \ "${API_URL}${URL}") response_status=$(echo "${response}" | sed -n '$p') response_content=$(echo "${response}" | sed '$d') echo "${response_status} ${response_content}" } getJSONFieldString() { local json field result json="$1" field="$2" # shellcheck disable=SC1117 result=$(echo "${json}" | "${BASE_PATH}/${LIBS}/JSON.sh" | grep "\[\"${field}\"\]" | sed -r "s/\[\"${field}\"\]\s+(.*)/\1/") echo "${result:1:${#result}-2}" } # set CURRENT_PATH with profile name # usage: initProfile |set|get] profile_name # set: create the profile if missing # get: raise an error if no profile with that name initProfile() { local create_profile=$1 local profile=$2 if [[ ! -d "${PROFILES_PATH}" ]]; then mkdir -pv "${PROFILES_PATH}" || exit 1 fi # checking if some profiles remains in legacy profile path local legacy_profiles= local legacy_default_profile= if [[ -d "${LEGACY_PROFILES_PATH}" ]]; then # is there any profile in legacy path ? legacy_profiles=$(ls -A "${LEGACY_PROFILES_PATH}" 2>/dev/null) legacy_default_profile=$(cd "${BASE_PATH}" && ls .ovh* access.rules 2>/dev/null) if [[ -n "${legacy_profiles}" ]] || [[ -n "${legacy_default_profile}" ]]; then # notify about migration to new location: _echoWarning "Your profiles were in the legacy path, migrating to ${PROFILES_PATH}:" if [[ -n "${legacy_default_profile}" ]]; then _echoWarning "> migrating default profile:" echo "${legacy_default_profile}" mv "${BASE_PATH}"/{.ovh*,access.rules} "${PROFILES_PATH}" fi if [[ -n "${legacy_profiles}" ]]; then _echoWarning "> migrating custom profiles:" echo "${legacy_profiles}" mv "${LEGACY_PROFILES_PATH}"/* "${PROFILES_PATH}" fi fi fi # if profile is not set, or with value 'default' if [[ -z "${profile}" ]] || [[ "${profile}" == "default" ]]; then # configuration stored in the profile main path CURRENT_PATH="${PROFILES_PATH}" else # ensure profile directory exists if [[ ! -d "${PROFILES_PATH}/${profile}" ]]; then case ${create_profile} in get) echo "${PROFILES_PATH}/${profile} should exists" listProfile exit 1 ;; set) mkdir "${PROFILES_PATH}/${profile}" || exit 1 ;; esac fi # override default configuration location CURRENT_PATH="$( cd "${PROFILES_PATH}/${profile}" && pwd )" fi if [[ -n "${profile}" ]]; then HELP_CMD="${HELP_CMD} --profile ${profile}" fi } listProfile() { local dir= echo "Available profiles: " echo "- default" if [[ -d "${PROFILES_PATH}" ]]; then # only list directory for dir in $(cd "${PROFILES_PATH}" && ls -d -- */ 2>/dev/null) do # display directory name without slash echo "- ${dir%%/}" done fi } # ensure OVH App Key an App Secret are defined hasOvhAppKey() { if [[ -z "${OVH_APP_KEY}" ]] && [[ -z "${OVH_APP_SECRET}" ]]; then echo -e "No application is defined for target ${TARGET}, please call to initialize it:\\n${HELP_CMD} --initApp" return 1 fi return 0 } main() { parseArguments "$@" # set to default value if empty TARGET=${TARGET:-"EU"} METHOD=${METHOD:-"GET"} URL=${URL:-"/me"} PROFILE=${PROFILE:-"default"} POST_DATA=${POST_DATA:-} readonly API_URL="${API_URLS[${TARGET}]}" local profileAction="get" if [[ -n "${INIT_KEY_ACTION}" ]]; then profileAction="set" fi initProfile "${profileAction}" "${PROFILE}" # user want to add An API Key case ${INIT_KEY_ACTION} in AppKey) createApp;; ConsumerKey) createConsumerKey;; esac ## exit after initializing any API Keys [[ -n "${INIT_KEY_ACTION}" ]] && exit 0 initApplication initConsumerKey if hasOvhAppKey; then if [[ -z "${OVH_CONSUMER_KEY}" ]]; then echo "No consumer key for target ${TARGET}, please call to initialize it:" echo "${HELP_CMD} --init" else request "${METHOD}" "${URL}" fi fi } main "$@"