diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index ad7b980..8b3926f 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -22,60 +22,14 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release .. make -j - - name: Upload release asset to Gitea + - name: Upload release assets to Gitea if: github.ref_type == 'tag' shell: bash env: GITEA_TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }} - GITEA_SERVER: ${{ github.server_url }} - REPO: ${{ github.repository }} - TAG: ${{ github.ref_name }} run: | set -euo pipefail - - file="build/libdurin-plugin.so.1.0.0" - if [ ! -f "$file" ]; then - echo "Release asset not found: $file" - exit 1 - fi - - api="${GITEA_SERVER}/api/v1/repos/${REPO}" - - release_json="$(curl -fsS \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "${api}/releases/tags/${TAG}" || true)" - - if [ -z "${release_json}" ] || [ "${release_json}" = "null" ]; then - echo "Release for tag ${TAG} does not exist, creating it" - release_json="$(curl -fsS \ - -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${api}/releases" \ - -d "{ - \"tag_name\": \"${TAG}\", - \"name\": \"${TAG}\", - \"draft\": false, - \"prerelease\": false - }")" - else - echo "Release for tag ${TAG} already exists" - fi - - release_id="$(printf '%s' "${release_json}" | sed -n 's/.*"id":[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1)" - - if [ -z "${release_id}" ]; then - echo "Failed to determine release id" - echo "${release_json}" - exit 1 - fi - - asset_name="$(basename "${file}")" - - echo "Uploading ${asset_name} to release ${release_id}" - curl -fsS \ - -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -F "name=${asset_name}" \ - -F "attachment=@${file};filename=${asset_name};type=application/octet-stream" \ - "${api}/releases/${release_id}/assets" \ No newline at end of file + python tools/gitea_release_upload.py \ + "${{ github.server_url }}" \ + "${{ github.repository }}" \ + "${{ github.ref_name }}" \ No newline at end of file diff --git a/tools/gitea_release_upload.py b/tools/gitea_release_upload.py new file mode 100644 index 0000000..316b782 --- /dev/null +++ b/tools/gitea_release_upload.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +import json +import mimetools +import mimetypes +import os +import sys +import urllib2 + + +def fail(msg, code=1): + sys.stderr.write("ERROR: %s\n" % msg) + sys.exit(code) + + +def api_request(url, token, method="GET", json_data=None, content_type=None): + data = None + headers = { + "Authorization": "token %s" % token, + } + + if json_data is not None: + data = json.dumps(json_data) + headers["Content-Type"] = "application/json" + elif content_type is not None: + headers["Content-Type"] = content_type + + request = urllib2.Request(url, data=data, headers=headers) + request.get_method = lambda: method + + try: + response = urllib2.urlopen(request) + body = response.read() + status = response.getcode() + return status, body + except urllib2.HTTPError as e: + return e.code, e.read() + + +def encode_multipart_formdata(fields, files): + boundary = mimetools.choose_boundary() + crlf = "\r\n" + lines = [] + + for key, value in fields: + lines.append("--" + boundary) + lines.append('Content-Disposition: form-data; name="%s"' % key) + lines.append("") + lines.append(value) + + for key, filename, content, content_type in files: + lines.append("--" + boundary) + lines.append( + 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename) + ) + lines.append("Content-Type: %s" % content_type) + lines.append("") + lines.append(content) + + lines.append("--" + boundary + "--") + lines.append("") + body = crlf.join(lines) + content_type = "multipart/form-data; boundary=%s" % boundary + return content_type, body + + +def multipart_request(url, token, fields, files): + content_type, body = encode_multipart_formdata(fields, files) + headers = { + "Authorization": "token %s" % token, + "Content-Type": content_type, + } + + request = urllib2.Request(url, data=body, headers=headers) + request.get_method = lambda: "POST" + + try: + response = urllib2.urlopen(request) + return response.getcode(), response.read() + except urllib2.HTTPError as e: + return e.code, e.read() + + +def get_release_by_tag(api_base, token, tag): + url = "%s/releases/tags/%s" % (api_base, tag) + status, body = api_request(url, token, method="GET") + if status == 200: + return json.loads(body) + if status == 404: + return None + fail("failed to fetch release for tag %s: HTTP %s\n%s" % (tag, status, body)) + + +def create_release(api_base, token, tag): + url = "%s/releases" % api_base + payload = { + "tag_name": tag, + "name": tag, + "draft": False, + "prerelease": False, + } + status, body = api_request(url, token, method="POST", json_data=payload) + if status not in (200, 201): + fail("failed to create release for tag %s: HTTP %s\n%s" % (tag, status, body)) + return json.loads(body) + + +def ensure_release(api_base, token, tag): + release = get_release_by_tag(api_base, token, tag) + if release is not None: + print("Release for tag %s already exists (id=%s)" % (tag, release.get("id"))) + return release + + print("Release for tag %s does not exist, creating it" % tag) + release = create_release(api_base, token, tag) + print("Created release id=%s" % release.get("id")) + return release + + +def upload_asset(api_base, token, release_id, file_path): + if not os.path.isfile(file_path): + fail("file not found: %s" % file_path) + + asset_name = os.path.basename(file_path) + mime_type = mimetypes.guess_type(asset_name)[0] or "application/octet-stream" + + with open(file_path, "rb") as f: + content = f.read() + + url = "%s/releases/%s/assets" % (api_base, release_id) + status, body = multipart_request( + url, + token, + fields=[("name", asset_name)], + files=[("attachment", asset_name, content, mime_type)], + ) + + if status not in (200, 201): + fail("failed to upload asset %s: HTTP %s\n%s" % (asset_name, status, body)) + + print("Uploaded asset: %s" % asset_name) + + +def find_assets(build_dir): + names = [] + for name in sorted(os.listdir(build_dir)): + if name.startswith("libdurin-plugin.so"): + full = os.path.join(build_dir, name) + if os.path.isfile(full): + names.append(full) + return names + + +def main(): + if len(sys.argv) != 4: + sys.stderr.write( + "Usage: %s \n" % sys.argv[0] + ) + sys.stderr.write( + "Example: %s https://gitea.psi.ch mx/durin 1.0.0\n" % sys.argv[0] + ) + sys.exit(1) + + server = sys.argv[1].rstrip("/") + repo = sys.argv[2] + tag = sys.argv[3] + + token = os.environ.get("GITEA_TOKEN") + if not token: + fail("GITEA_TOKEN environment variable is not set") + + build_dir = "build" + if not os.path.isdir(build_dir): + fail("build directory not found: %s" % build_dir) + + assets = find_assets(build_dir) + if not assets: + fail("no libdurin-plugin.so* files found in %s" % build_dir) + + api_base = "%s/api/v1/repos/%s" % (server, repo) + + release = ensure_release(api_base, token, tag) + release_id = release.get("id") + if not release_id: + fail("release id missing in API response") + + for asset in assets: + upload_asset(api_base, token, release_id, asset) + + print("Done.") + + +if __name__ == "__main__": + main() \ No newline at end of file