appcenter-docs icon indicating copy to clipboard operation
appcenter-docs copied to clipboard

No instructions for how to upload a build via API

Open lbrownell-gpsw opened this issue 4 years ago • 29 comments

The page's introduction includes this:

On this page you can learn how to generate the binary for release, and how to upload and release it to groups using the portal, the command-line interface (CLI), and the application programming interface (API).

There is a CLI example, but no API example. Is it no longer supported?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

lbrownell-gpsw avatar Nov 19 '20 18:11 lbrownell-gpsw

+1 to this. Please can you restore the instructions that used to exist on this page. It used to be here and I'm not sure why you've removed it.

Please can you put it back.

Edit: I asked about this back in July #1083 but received no response.

mezpahlan avatar Dec 08 '20 12:12 mezpahlan

Where are the definitions of

{upload_domain}/upload/set_metadata/{package_asset_id} {upload_domain}/upload/upload_chunk/{package_asset_id} {upload_domain}/upload/finished/{package_asset_id}

?

mezpahlan avatar Dec 08 '20 13:12 mezpahlan

+1 to restoring the API instructions/example

robertmchardy avatar Dec 09 '20 10:12 robertmchardy

+1 to this. Please provide instructions on how to generate the upload URL via API and/or examples.

coll6905 avatar Dec 09 '20 15:12 coll6905

Thanks all for the report. Our product team has reported that, unfortunately, the API was deprecated, and in fact, the references here were traces that were missed when the APIs were removed: https://github.com/MicrosoftDocs/appcenter-docs-pr/commit/0b8451a31f3e2adf93f6392e8f4d4afa915556ce

I'm authoring a PR to clean those up to prevent future confusion. The team recommends using the CLI instead.

King-of-Spades avatar Dec 09 '20 16:12 King-of-Spades

Hi @King-of-Spades can you confirm a few things please.

How are users that use the API directly via the Swagger specs supposed to know how to orchestrate an upload? The use case for me is that I maintain the Jenkins AppCenter plugin and the instructions that used to exist on this page were valuable in knowing what to call and when. However this also has an impact on anyone else writing a script using the previously published Swagger APIs.

You (AppCenter / MS) have clearly introduced new APIs that are critical to the upload (distribute) process. I've mentioned them in my comment above - https://github.com/MicrosoftDocs/appcenter-docs/issues/1172#issuecomment-740624098. Are these going to be documented or not?

mezpahlan avatar Dec 09 '20 17:12 mezpahlan

I'll double-check with the team.

King-of-Spades avatar Dec 09 '20 18:12 King-of-Spades

@mezpahlan Here is a pr for change made in Fastlane plugin to use new apis. Would that help you to make similar changes in Jenkins Plugin?

https://github.com/microsoft/fastlane-plugin-appcenter/pull/230/files

atkhalsa avatar Dec 09 '20 20:12 atkhalsa

Hi @atkhalsa thanks. I'm basing my changes from the fastlane plugin already. So yes that does help.

However I have to reverse engineer an existing plugin written in a language I'm not familiar with when there used to exist perfectly good documentation. I understand you want people to use CLI but for myself (and anyone else building a script) AppCenter / MS have broken something that used to work (I'm talking about the documentation) and I don't understand the rationale behind that decision.

Given a Swagger spec exists I think all people (including myself) are looking for is some guidance on what APIs to call and in what order like the old revision of the documentation used to have.

Of course, if you would like to contribute to the Jenkins plugin or adopt it then that would also work for me :rofl: :rofl: :rofl:

mezpahlan avatar Dec 09 '20 21:12 mezpahlan

Let me discuss with the team and see what can be done

atkhalsa avatar Dec 09 '20 22:12 atkhalsa

@mezpahlan

Here is a status update that would interest you :)

https://status.appcenter.ms/incidents/7930rkfgtg1y

There is another good news that someone from the team is going to help you with the code change needed in Jenkin's plugin to make it work with the new flow.

Sorry for the inconvenience and thank you for your patience.

atkhalsa avatar Dec 10 '20 00:12 atkhalsa

That's amazing. Thank you so much ☺️.

mezpahlan avatar Dec 10 '20 08:12 mezpahlan

Wow Jenkins plugin...I shoulda known. We have a python script that implements the old API for upload/distribute; I just re-wrote it using the CLI, but will keep an eye out for the plugin to get updated.

Kudos to the Fastlane team for helping a Jenkins brother out, too!

lbrownell-gpsw avatar Dec 11 '20 21:12 lbrownell-gpsw

Is there any update on the documentation for the new APIs? The status update mentioned that there'd be documentation for uploading releases with the new API for developers that cannot use the CLI, but I don't see that uploaded anywhere yet, and we are fairly close to February 1.

@King-of-Spades

ocastillo346 avatar Jan 19 '21 15:01 ocastillo346

I'm finishing the final draft currently, expecting to have it done by EOD.

King-of-Spades avatar Jan 19 '21 16:01 King-of-Spades

@ocastillo346 The instructions are live now. I've tested them thoroughly to confirm they work, but it's possible in writing the guide I made some error or omission so let me know if you get stuck (including details of the step & error message) and I might be able to refine any pain points a little further.

King-of-Spades avatar Jan 20 '21 03:01 King-of-Spades

@King-of-Spades Hi, I noticed an ambiguity with different urls on page https://docs.microsoft.com/en-us/appcenter/distribution/uploading:
"Finally, release the build. The endpoint to call is PATCH /v0.1/apps/{owner_name}/{app_name}/release_uploads/{upload_id}" (which is deprecated by the way)
but script is RELEASE_STATUS_URL="https://api.appcenter.ms/v0.1/apps/$OWNER_NAME/$APP_NAME/uploads/releases/$UPLOAD_ID"

blinkev avatar Jan 21 '21 14:01 blinkev

Thanks, I'm working on updating it, looks like I was still using the link from the old version 😅

King-of-Spades avatar Jan 21 '21 16:01 King-of-Spades

@blinkev I updated the doc yesterday afternoon, I hit some delays due to unrelated publishing issues and fine-tuning a few different areas. If you notice any other discrepancies, or the update was insufficient let me know.

This set of steps has been especially tricky to document just right.

King-of-Spades avatar Jan 22 '21 16:01 King-of-Spades

@King-of-Spades Stage 6 still looks strange. According to fastlane plugin appcenter https://github.com/microsoft/fastlane-plugin-appcenter/blob/2b8aef1aa9a75faf09f14428d50d65d9d35b34e7/lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb#L227
there should be polling after stage 5 until we get upload_status == "readyToBePublished" and get release_distinct_id which is used to release distribute

blinkev avatar Jan 25 '21 11:01 blinkev

Working python script will be:

# coding=utf-8
import base64, requests, os, sys, random, subprocess, time

# HTTP Logs
# httplib.HTTPConnection.debuglevel = 2

POLLING_TIMEOUT_MINUTES = 5
POLLING_INTERVAL_SECONDS = 5

artifact_file = sys.argv[1]
token = sys.argv[2]
app_name = sys.argv[3]
release_group_id = sys.argv[4]

default_headers = {
    "X-API-Token" : token,
    "Accept" : "application/json",
    "Content-Type" : "application/json"
}

print
print("-------- Prepare Upload Stage --------")
request = "https://api.appcenter.ms/v0.1/apps/your_organization/%s/uploads/releases" % app_name
print("POST: %s" % request)
response = requests.post(request, headers = default_headers)
print("Response code: %s" % response.status_code)
print("Response body: %s" % response.content)
release_id = response.json()['id']
package_asset_id = response.json()['package_asset_id']
url_encoded_token = response.json()['url_encoded_token']
upload_domain = response.json()['upload_domain']

print
print("-------- Get Upload Id Stage --------")
apk_size = os.path.getsize(artifact_file)
content_type = "application/vnd.android.package-archive"
file_name = os.path.basename(artifact_file)
request = "https://file.appcenter.ms/upload/set_metadata/%s?file_name=%s&file_size=%s&token=%s&content_type=%s" % (package_asset_id, file_name, apk_size, url_encoded_token, content_type)
print("POST: %s" % request)
response = requests.post(request, headers = default_headers)
print("Response code: %s" % response.status_code)
print("Response body: %s" % response.content)
upload_id = response.json()['id']
chunk_size = response.json()['chunk_size']

print
print("-------- Upload Artifact Chunks Stage --------")
artifact_dir = os.path.dirname(artifact_file)
chunks_dir = artifact_dir + "/chunks"
os.mkdir(chunks_dir)
chunk_name = chunks_dir + "/chunk_"
subprocess.call(['split', '-b', str(chunk_size), artifact_file, chunk_name])

block_number = 0
for chunk_name in sorted(os.listdir(chunks_dir)):
    print
    block_number += 1
    chunk_path = os.path.join(chunks_dir, chunk_name)
    chunk_size = os.path.getsize(chunk_path)
    print("chunk path: %s" % chunk_path)
    print("chunk size: %s" % chunk_size)
    headers = {
        "Content-Length": str(chunk_size),
        "Content-Type": "application/octet-stream"
    }
    request = "%s/upload/upload_chunk/%s?token=%s&block_number=%s" % (upload_domain, package_asset_id, url_encoded_token, block_number)
    print("POST: %s" % request)
    response = requests.post(request, data = open(chunk_path, 'rb'), headers = headers)
    print("Response code: %s" % response.status_code)
    print("Response body: %s" % response.content)

print
print("-------- Upload Complete Stage --------")
request = "https://file.appcenter.ms/upload/finished/%s?token=%s" % (package_asset_id, url_encoded_token)
print("POST: %s" % request)
response = requests.post(request, headers = default_headers)
print("Response code: %s" % response.status_code)
print("Response body: %s" % response.content)

print
print("-------- Commit Upload Stage --------")
request = "https://api.appcenter.ms/v0.1/apps/your_organization/%s/uploads/releases/%s" % (app_name, release_id)
body = {
    "upload_status": "uploadFinished",
    "id": release_id
}
print("PATCH: %s" % request)
response = requests.patch(request, headers = default_headers, json = body)
print("Response code: %s" % response.status_code)
print("Response body: %s" % response.content)

print
print("-------- Get Release Number Stage --------")
request = "https://api.appcenter.ms/v0.1/apps/your_organization/%s/uploads/releases/%s" % (app_name, release_id)
timeout = time.time() + 60 * POLLING_TIMEOUT_MINUTES
print("Starting polling...")
while True:
    print
    print("GET: %s" % request)
    response = requests.get(request, headers = default_headers)
    print("Response code: %s" % response.status_code)
    print("Response body: %s" % response.content)
    if response.json()['upload_status'] == "readyToBePublished":
        release_number = response.json()['release_distinct_id']
        break
    elif time.time() > timeout:
        print("Cannot get release number. Quit...")
        quit()
    else:
        time.sleep(POLLING_INTERVAL_SECONDS)

print
print("-------- Distribute Stage --------")
request = "https://api.appcenter.ms/v0.1/apps/your_organization/%s/releases/%s/groups" % (app_name, release_number)
print("POST: %s" % request)
body = {
    "id": release_group_id,
    "mandatory_update": False,
    "notify_testers": False
}
response = requests.post(request, headers = default_headers, json = body)
print("Response code: %s" % response.status_code)
print("Response body: %s" % response.content)

substitute "your_organization" with your value and run a script like:
python app_center_upload.py your_apk_file.apk api_token app_name release_group_id

blinkev avatar Jan 25 '21 11:01 blinkev

@blinkev Thanks for that catch, you're right, I should include info on polling the API to see if it's ready, since that's key for any script incorporating the entire process.

King-of-Spades avatar Jan 25 '21 17:01 King-of-Spades

Hi. I am going to piggyback on this. Hope it is all right. Regarding Step 6, the example given is wrong, where is just a simple GET request

RELEASE_STATUS_URL="https://api.appcenter.ms/v0.1/apps/$OWNER_NAME/$APP_NAME/uploads/releases/$UPLOAD_ID" curl -s -H "Content-Type: application/json" -H "Accept: application/json" -H "X-API-Token: $API_TOKEN" $RELEASE_STATUS_URL

i am getting this error from the server:

{"message":"Validation errors","errors":[{"code":"INVALID_REQUEST_PARAMETER","errors":[{"code":"OBJ ECT_MISSING_REQUIRED_PROPERTY","params":["upload_status"],"message":"Missing required property: upload_status","path":[]}],"in":"body","message":"Invalid parameter (body): Value failed JSON Schema validation","name":"body","path":["paths","/apps/{app_id}/uploads/releases/{upload_id}","patch","parameters","2"]}]}

Looking at the referenced API, PATCH /v0.1/apps/{owner_name}/{app_name}/uploads/releases/{upload_id} it seems there is a required body. Question is, what should the upload_status be? We use uploadFinished in a step or two above

narciszait avatar Jan 30 '21 01:01 narciszait

@narciszait I believe I found the discrepancy and published an update. If you're still stuck on this, refresh to doc and let me know if the new 6 & 7 steps still aren't working for you.

In particular, it looks like I was inconsistent with the variable to use 😅

King-of-Spades avatar Feb 03 '21 16:02 King-of-Spades

@King-of-Spades i got it working. Basically I skip over step 7 and call another API where I put in the release notes and then get the distributions groups and distribute to each group. My misunderstanding was step 7: where you patch with a body of "destination ....", while OpenApi AppCenter, for that patch API call has "upload_status": "uploadFinished" as example - do you see my confusion?

narciszait avatar Feb 03 '21 16:02 narciszait

@narciszait Thanks for that. So, I definitely agree that "upload_status": "uploadFinished" feels counter-intuitive.

That step is needed in order to confirm to App Center that you've finished uploading your app chunks. (I haven't tested how the API responds to extra chunks or missing chunks when finished).

That said, I did introduce an inconsistency in variable naming on the last two steps, so that may have also contributed if you were attempting an identical approach.

King-of-Spades avatar Feb 03 '21 17:02 King-of-Spades

@King-of-Spades I think put shortly, Step 5 and Step 7 call the same API, with a PATCH call, but with different bodies.

"upload_status": "uploadFinished" happens in Step 5, with exactly what is shown as example in the attached screenshot. Screenshot 2021-02-03 at 20 00 59 I am referring to this part of Step 5:

COMMIT_URL="https://api.appcenter.ms/v0.1/apps/$OWNER_NAME/$APP_NAME/uploads/releases/$UPLOAD_ID" curl -H "Content-Type: application/json" -H "Accept: application/json" -H "X-API-Token: $API_TOKEN" \ --data '{"upload_status": "uploadFinished","id": "$ID"}' \ -X PATCH \ $COMMIT_URL

Maybe, for step 7, it should be mentioned that it makes the same API call (the COMMIT_URL) as in Step 5, but the body of the PATCH call is different this time.

Does it make sense?

narciszait avatar Feb 03 '21 19:02 narciszait

@King-of-Spades regarding when you missed a chunk to upload, this is how the server responds:

Finalize upload (5/n) finishedUploadResponse {"error":true,"chunk_num":0,"error_code":"MissingChunk","message":"Verification failed. Missing chunks detected, please resend missing chunks.","missing_chunks":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43]}

I sent the finishedUpload call and I was under the impression i uploaded every chunk, but not even the first one was uploaded.

narciszait avatar Feb 04 '21 08:02 narciszait

@narciszait you're right, I see it now! I had to double-check my QA build to make sure I hadn't just screwed up the code at some point and there is some repetition.

Originally when optimizing I tried to minimize the repetition to keep things simple but I missed this.

King-of-Spades avatar Feb 04 '21 16:02 King-of-Spades