Skip to content

Instantly share code, notes, and snippets.

@stefanbuck
Last active November 26, 2023 12:40
Show Gist options
  • Save stefanbuck/ce788fee19ab6eb0b4447a85fc99f447 to your computer and use it in GitHub Desktop.
Save stefanbuck/ce788fee19ab6eb0b4447a85fc99f447 to your computer and use it in GitHub Desktop.
Script to upload a release asset using the GitHub API v3.
#!/usr/bin/env bash
#
# Author: Stefan Buck
# License: MIT
# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447
#
#
# This script accepts the following parameters:
#
# * owner
# * repo
# * tag
# * filename
# * github_api_token
#
# Script to upload a release asset using the GitHub API v3.
#
# Example:
#
# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip
#
# Check dependencies.
set -e
xargs=$(which gxargs || which xargs)
# Validate settings.
[ "$TRACE" ] && set -x
CONFIG=$@
for line in $CONFIG; do
eval "$line"
done
# Define variables.
GH_API="https://api.github.com"
GH_REPO="$GH_API/repos/$owner/$repo"
GH_TAGS="$GH_REPO/releases/tags/$tag"
AUTH="Authorization: token $github_api_token"
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie"
CURL_ARGS="-LJO#"
if [[ "$tag" == 'LATEST' ]]; then
GH_TAGS="$GH_REPO/releases/latest"
fi
# Validate token.
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }
# Read asset tags.
response=$(curl -sH "$AUTH" $GH_TAGS)
# Get ID of the asset based on given filename.
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
# Upload asset
echo "Uploading asset... "
# Construct url
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)"
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET
@trevorsandy
Copy link

trevorsandy commented Feb 11, 2020

This block is failing to retrieve my asset id - returns "".

# Get ID of the asset based on given filename.
id=""
eval $(echo "$response" | grep -C1 "name.:.\+$filename" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
assert_id="$id"

jq did the trick however - with a lot less code and complexity:

# Get ID of the release.
release_id="$(echo $response | jq -r .id)"

# Get ID of the asset based on given filename.
asset_id="$(echo $response | jq -r '.assets[] | select(.name == '\"$ASSET\"').id')"

Cheers,

@typebrook
Copy link

typebrook commented Feb 12, 2020

@allysono

Error: Invalid repo, token or network issue!

  • First thing first, check you repo is on github.com or your enterprise server github.<company>.com
  • Second, check your token has scope with repo in web page https://github.com/settings/tokens, or maybe https://github<company>.com/settings/tokens

@janiez
Copy link

janiez commented Feb 24, 2020

Good job!!

@zwhitchcox
Copy link

What is the variable $GITHUB_OAUTH_BASIC supposed to be?

@zwhitchcox
Copy link

Oh never mind, I see, it's if you want to add your actual username and password as an option

@zwhitchcox
Copy link

Delete asset if exists using jq

sudo apt install jq

# Read asset tags.
response=$(curl -sH "$AUTH" $GH_TAGS)
# Get ID of the release.
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
release_id="$id"

# ------New Code starts Here------
# Get ID of the asset based on given filename.
id=""
for row in $(echo $response | jq '.assets | map({name: .name, id: .id})' | jq -c '.[]'); do
  name=$(echo ${row} | jq -r '.name')
  if [ $name == $filename ]; then
    asset_id=$(echo ${row} | jq -r '.id')
    echo "Deleting asset($asset_id)... "
    DELETE_URL="https://api.github.com/repos/${owner}/${repo}/releases/assets/${asset_id}"
    curl  -X "DELETE" -H "Authorization: token $github_api_token" "${DELETE_URL}"
  fi
done


# Upload asset
echo "Uploading asset... "

# Construct url
GH_ASSET="https://uploads.github.com/repos/${owner}/${repo}/releases/${release_id}/assets?name=$(basename ${filename})"
echo $GH_ASSET

curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET
echo

@SjarletK
Copy link

SjarletK commented Jun 17, 2021

one little correction:

(...)
for row in $(echo $response | jq '.assets | map({name: .name, id: .id})' | jq -c '.[]'); do
  name=$(echo ${row} | jq -r '.name')
  if [ $name == basename $filename ]; then
    asset_id=$(echo ${row} | jq -r '.id')
    echo "Deleting asset($asset_id)... "
    DELETE_URL="https://api.github.com/repos/${owner}/${repo}/releases/assets/${asset_id}"
    curl  -X "DELETE" -H "Authorization: token $github_api_token" "${DELETE_URL}"
  fi
done
(...)

@lilianmoraru
Copy link

@awais786327 I recommend using GitHub Actions for this task https://github.com/actions/upload-release-asset nowadays.

Github Actions is not available on our enterprise server.
This script is still quite useful, thanks.

@CaliforniaMountainSnake
Copy link

Got a curl: (3) URL using bad/illegal format or missing URL error, but this script seems to be working!

@sfuerte
Copy link

sfuerte commented Jun 8, 2022

just quick 2c:
if jq is available, then it's much easier, faster and less error-prone to extract IDs and URLs via it.
Here is a response example:

$ curl https://api.github.com/repos/hashicorp/packer/releases/latest
{
  "url": "https://api.github.com/repos/hashicorp/packer/releases/68048553",
  "assets_url": "https://api.github.com/repos/hashicorp/packer/releases/68048553/assets",
  "upload_url": "https://uploads.github.com/repos/hashicorp/packer/releases/68048553/assets{?name,label}",
  "html_url": "https://github.com/hashicorp/packer/releases/tag/v1.8.1",
  "id": 68048553,
...

Then an upload URL and its usage transforms to:

UPLOAD_URL=$(curl -sH "${GH_AUTH}" "$(GITHUB_REPO)/releases/tags/$(VERSION_FULL)" \
	| jq -r '.upload_url' | cut -d'{' -f1)

curl -X POST \
	-H "${GH_AUTH}" \
	-H "Accept: application/vnd.github.v3+json" \
	-H "Content-Type: $(file -b --mime-type ${FILE})" \
	-H "Content-Length: $(wc -c <${FILE} | xargs)" \
	-T "${FILE}" \
	"${UPLOAD_URL}?name=$(basename ${FILE})" | cat

Also, it automatically works for enterprise environments with custom URLs, e.g. in our environment uploads.github.<corp FQDN> is not available and github.<corp FQDN>/api/uploads/ is used instead.

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