-
-
Save miklosboros/4855a59213724f6eb5960579e15d285b to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| # | |
| # Uploads an installable artifact to Bitrise using Release Management Public API. | |
| # Reference: https://api.bitrise.io/release-management/api-docs/index.html#/Installable%20Artifacts%20-%20COMING%20SOON/GenerateInstallableArtifactUploadUrl | |
| # | |
| # This script supports Linux distributions (alpine, arch, centos, debian, fedora, rhel, ubuntu) and macOS. | |
| # For it to work properly you will need either jq and openssl packages installed on your system or sudo privileges for the script. | |
| # | |
| # You need a couple of environment variables to set up and you can call this script from terminal: | |
| # ARTIFACT_PATH=LOCAL_PATH_OF_THE_ARTIFACT_TO_BE_UPLOADED \ | |
| # AUTHORIZATION_TOKEN=BITRISE_RM_API_ACCESS_TOKEN \ | |
| # CONNECTED_APP_ID=APP_ID_OF_THE_CONNECTED_APP_THE_ARTIFACT_WILL_BE_UPLOADED_TO \ | |
| # /bin/bash ./scripts/upload_installable_artifact.sh | |
| ####################################### | |
| # Checks for script dependencies. If the OS is not supported it exits. Missing dependencies (jq, openssl) are installed. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # None | |
| ####################################### | |
| check_dependencies() { | |
| if [[ $(is_macOS) -eq 1 ]] && [[ $(linux_distro) -eq 1 ]]; then | |
| echo "Unsupported OS. Exiting..." | |
| exit 1 | |
| fi | |
| if [[ $(check_command_installed "jq") -eq 1 ]]; then | |
| install_command "jq" | |
| fi | |
| if [[ $(check_command_installed "openssl") -eq 1 ]]; then | |
| install_command "openssl" | |
| fi | |
| } | |
| ####################################### | |
| # Checks Whether the given command is installed or not. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # The command to check for. | |
| # Outputs: | |
| # Returns zero if the command is installed and 1 if not. | |
| ####################################### | |
| check_command_installed() { | |
| if command -v "$1" > /dev/null 2>&1; then | |
| echo 0 | |
| else | |
| echo 1 | |
| fi | |
| } | |
| ####################################### | |
| # Gets the information needed for uploading an installable artifact from Release Management Public API. | |
| # Globals: | |
| # AUTHORIZATION_TOKEN | |
| # ARTIFACT_PATH | |
| # CONNECTED_APP_ID | |
| # Arguments: | |
| # UUID for the artifact to be uploaded. | |
| # Outputs: | |
| # Returns the upload information including headers, method and url. | |
| ####################################### | |
| get_upload_information() { | |
| if [[ $(linux_distro) -ne 1 ]]; then | |
| file_size_bytes=$(stat -c%s "$ARTIFACT_PATH") | |
| else | |
| file_size_bytes=$(stat -f%z "$ARTIFACT_PATH") | |
| fi | |
| file_name=$(echo "\"$ARTIFACT_PATH\"" | jq -r 'split("/") | .[-1]') | |
| upload_info=$(curl -s -H "Authorization: $AUTHORIZATION_TOKEN" "$RM_API_HOST/release-management/v1/connected-apps/$CONNECTED_APP_ID/installable-artifacts/$1/upload-url?file_name=$file_name&file_size_bytes=$file_size_bytes") | |
| echo "$upload_info" | |
| } | |
| ####################################### | |
| # Installs a missing command to the machine. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # The command to be installed. | |
| ####################################### | |
| install_command() { | |
| if [[ $(is_macOS) -eq 0 ]]; then | |
| if command -v brew > /dev/null 2>&1; then | |
| brew install "$1" | |
| else | |
| echo "Homebrew is not installed. Please install Homebrew first." | |
| exit 1 | |
| fi | |
| else | |
| distro=$(linux_distro) | |
| printf "Detected Linux distribution: %s.\n" "$(distro)" | |
| case "$distro" in | |
| ubuntu|debian) | |
| sudo apt update && sudo apt install -y "$1" | |
| ;; | |
| fedora) | |
| sudo dnf install -y "$1" | |
| ;; | |
| centos|rhel) | |
| sudo yum install -y "$1" | |
| ;; | |
| alpine) | |
| sudo apk add --no-cache "$1" | |
| ;; | |
| arch) | |
| sudo pacman -Sy "$1" | |
| ;; | |
| esac | |
| fi | |
| } | |
| ####################################### | |
| # Checks whether the script is running on MacOS or not. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # None | |
| # Outputs: | |
| # Returns zero if MacOS found, 1 otherwise. | |
| ####################################### | |
| is_macOS() { | |
| if [ "$(uname)" == "Darwin" ]; then | |
| echo 0 | |
| else | |
| echo 1 | |
| fi | |
| } | |
| ####################################### | |
| # Continuously checks whether the already uploaded artifact is processed by Release Management or not. | |
| # After successful processing, you can use the uploaded artifact in your releases and test distributions. | |
| # The function returns with a failure after a pre-defined retry count. | |
| # This is a recursive function calling itself four times after the first try. | |
| # Globals: | |
| # AUTHORIZATION_TOKEN | |
| # CONNECTED_APP_ID | |
| # Arguments: | |
| # UUID for the artifact to be uploaded. | |
| # Retry count. | |
| ####################################### | |
| is_processed() { | |
| if [[ $2 == 4 ]]; then | |
| echo "The artifact is still not processed after $2 retries. Exiting..." | |
| exit 1 | |
| fi | |
| status_data=$(curl -s -H "Authorization: $AUTHORIZATION_TOKEN" "$RM_API_HOST/release-management/v1/connected-apps/$CONNECTED_APP_ID/installable-artifacts/$1/status") | |
| request_error "$status_data" | |
| status=$(echo "$status_data" | jq -r '.status') | |
| if [[ "$status" == "processed_valid" ]] || [[ "$status" == "processed_invalid" ]]; then | |
| echo "$status_data" | |
| exit 0 | |
| elif [[ "$status" == "uploaded" ]] || [[ "$status" == "upload_requested" ]]; then | |
| echo "$status_data" | |
| sleep 1 | |
| is_processed "$1" $2 + 1 | |
| else | |
| echo "Unexpected status: $status. Exiting..." | |
| exit 1 | |
| fi | |
| } | |
| ####################################### | |
| # Checks the Linux distribution the script is running on. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # None | |
| # Outputs: | |
| # Returns the id of the OS distribution and zero if a supported distribution is found. Returns 1 if not a Linux. | |
| ####################################### | |
| linux_distro() { | |
| if [ -f /etc/os-release ]; then | |
| id=$(grep -E '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"') | |
| case "$id" in ubuntu|debian|fedora|centos|rhel|alpine|arch) | |
| echo "$id" | |
| ;; | |
| *) | |
| echo "Unsupported Linux distribution. Exiting..." | |
| exit 1 | |
| ;; | |
| esac | |
| else | |
| echo 1 | |
| fi | |
| } | |
| ####################################### | |
| # Processes the response of Google Cloud Storage when the upload request has been sent. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # The upload response. | |
| # The artifact UUID used for uploading. | |
| # Outputs: | |
| # Returns upload http status and response body from the upload request. | |
| process_upload_response() { | |
| http_status_code="${1:${#1}-3}" | |
| if [[ "$http_status_code" == 200 ]]; then | |
| is_processed "$2" 0 | |
| else | |
| response_body="${1:0:${#1}-3}" | |
| printf "upload http status: %s\n" "$http_status_code" | |
| echo "$response_body" | |
| fi | |
| } | |
| ####################################### | |
| # Checks for a request error coming from Release Management Public API. | |
| # Globals: | |
| # None | |
| # Arguments: | |
| # The response body. | |
| # Outputs: | |
| # Returns the error if there is an error. | |
| request_error() { | |
| error_code=$(echo "$1" | jq '.code' ) | |
| if [ "$error_code" == "null" ]; then | |
| return | |
| fi | |
| echo "$1" | |
| exit 0 | |
| } | |
| ####################################### | |
| # Uploads the installable artifact to Google Cloud Storage using the information given by Release Management Public API. | |
| # Globals: | |
| # The artifact path which contains the file to be uploaded. | |
| # Arguments: | |
| # The upload information given by Release Management Public API. | |
| # Outputs: | |
| # Returns the response of Google Cloud Storage. | |
| upload_artifact() { | |
| headers_json=$(echo "$1" | jq -r '.headers | to_entries | map("\(.value.name): \(.value.value)")') | |
| method=$(echo "$1" | jq -r '.method') | |
| url=$(echo "$1" | jq -r '.url') | |
| # read headers into bash array from jq array | |
| headers=() | |
| while IFS= read -r line; do | |
| headers+=($line) | |
| done <<< "$headers_json" | |
| # sanitize headers | |
| for ((i = 0; i < ${#headers[@]}; i++)); do | |
| headers[i]="${headers[i]//\"/}" | |
| headers[i]="${headers[i]%,}" | |
| done | |
| # build curl command | |
| curl_command="curl -sw \"%{http_code}\" -o - -X \"$method\"" | |
| for ((i = 1; i + 1 < ${#headers[@]}; i+=2)); do | |
| curl_command+=" -H \"${headers[i]} ${headers[i+1]}\"" | |
| done | |
| curl_command+=" --upload-file \"$ARTIFACT_PATH\" \"$url\"" | |
| eval "$curl_command" | |
| } | |
| check_dependencies | |
| uuid=$(openssl rand -hex 16) | |
| installable_artifact_id=${uuid:0:8}-${uuid:8:4}-${uuid:12:4}-${uuid:16:4}-${uuid:20:12} | |
| if [ -z "$RM_API_HOST" ]; then | |
| RM_API_HOST="https://api.bitrise.io" | |
| fi | |
| upload_info=$(get_upload_information "$installable_artifact_id") | |
| request_error "$upload_info" | |
| upload_response=$(upload_artifact "$upload_info") | |
| process_upload_response "$upload_response" "$installable_artifact_id" |
@miklosboros I think there might be a problem with this line: https://gist.github.com/miklosboros/4855a59213724f6eb5960579e15d285b#file-bitrise_upload_installable_artifact-sh-L66. In the comments it says:
Returns 1 if not a Linux., howeverstat -c%sis the correct command for Linux distros. It should be the inverse logic.
I think you are right and it works properly only because the linux_distro() gives back bad response.
I only found this, because I ran this script in an alpine docker image and it told me, that the stat properties are wrong. So I think, currently this script does not work with Linux distros.
You were right. I have fixed a couple of problems for Linux distros, but I suggest to use the more "official" script maintained within our company for this: Upload script. This has only been created recently.
However, you might have problems within a docker image as 'sudo' is not always available there to install commands (if you already have jq and openssl installed within the container it won't matter tho.
I will delete this gist in a couple of weeks to not have to maintain this in two places.
@miklosboros
I think there might be a problem with this line: https://gist.github.com/miklosboros/4855a59213724f6eb5960579e15d285b#file-bitrise_upload_installable_artifact-sh-L66.
In the comments it says:
Returns 1 if not a Linux., howeverstat -c%sis the correct command for Linux distros. It should be the inverse logic.