push(\GuzzleHttp\Middleware::retry( function ($retries, $request, $response) { $statusCode = $response->getStatusCode(); if ($retries < MAX_ATTEMPTS && !in_array($statusCode, [200, 201, 202])) { echo "Attempt failed with status code {$statusCode}: " . $response->getBody(); return true; } return false; }, function ($retries) { return 1000 * (1 + $retries); } )); // Create a GitHub client. $client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.github.com/', 'headers' => ['Authorization' => "token $token"], 'handler' => $stack, ]); // Create a Github client with no retry middleware, to allow for custom retry handling $uploadClient = new GuzzleHttp\Client([ 'base_uri' => 'https://api.github.com/', 'headers' => ['Authorization' => "token $token"] ]); // Publish the release $response = $client->post("repos/${owner}/${repo}/releases", [ 'json' => [ 'tag_name' => $tag, 'name' => "Version {$tag}", 'body' => $message, ] ]); $releaseBody = json_decode($response->getBody(), true); // Grab the location of the new release $url = $response->getHeaderLine('Location'); echo "Release successfully published to: $url\n"; // Uploads go to uploads.github.com $uploadUrl = new Uri($url); $uploadUrl = $uploadUrl->withHost('uploads.github.com'); // Upload aws.zip $zipAttempts = retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, 'aws.zip'); if ($zipAttempts === false) { echo "aws.zip upload failed after " . MAX_ATTEMPTS . " attempts.\n"; } else { echo "aws.zip upload succeeded after {$zipAttempts} attempt(s).\n"; } // Upload aws.phar $pharAttempts = retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, 'aws.phar'); if ($pharAttempts === false) { echo "aws.phar upload failed after " . MAX_ATTEMPTS . " attempts.\n"; } else { echo "aws.phar upload succeeded after {$pharAttempts} attempt(s).\n"; } /** * Attempts an artifact upload and retries up to MAX_ATTEMPTS times * * @param $client * @param $uploadClient * @param $owner * @param $repo * @param $releaseBody * @param $uploadUrl * @param $filename * @return bool|int */ function retryUpload($client, $uploadClient, $owner, $repo, $releaseBody, $uploadUrl, $filename) { $isSuccessful = false; $attempts = 0; $filetype = substr($filename, strpos($filename, '.') + 1); while (!$isSuccessful && $attempts < MAX_ATTEMPTS) { try { $attempts++; $response = $uploadClient->post("{$uploadUrl}/assets?name={$filename}", [ 'headers' => ['Content-Type' => "application/{$filetype}"], 'body' => Psr7\Utils::tryFopen(__DIR__ . "/artifacts/{$filename}", 'r') ]); echo "{$filename} uploaded to: " . json_decode($response->getBody(), true)['browser_download_url'] . "\n"; $isSuccessful = true; } catch (\GuzzleHttp\Exception\ServerException $e) { echo "{$filename} failed to upload:\n"; var_dump($e->getMessage()); // Fetch and inspect assets for failed downloads $response = $client->get("/repos/{$owner}/{$repo}/releases/{$releaseBody['id']}/assets", []); $assets = json_decode($response->getBody(), true); foreach ($assets as $asset) { // Only successful uploads have a state of 'uploaded' if ($asset['state'] !== 'uploaded') { try { // Failed uploads leave behind a corrupted artifact that must be deleted $response = $uploadClient->delete("/repos/{$owner}/{$repo}/releases/assets/{$asset['id']}", []); // Currently the successful 204 will trigger an exception due to response formatting, // but keeping this in case Github changes their API response if ($response->getStatusCode() == 204) { echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} has successfully been deleted.\n"; } else { echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} was unable to be deleted.\n"; } } catch (\GuzzleHttp\Exception\ClientException $e) { // Currently expected to generate an exception every time, even for success $response = $e->getResponse(); if ($response->getStatusCode() == 204) { echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} has successfully been deleted.\n"; } else { echo "Failed upload of {$asset['name']} at {$asset['browser_download_url']} was unable to be deleted.\n"; var_dump($e); } } } } } } if ($isSuccessful) { return $attempts; } return false; }