-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨😈☝️ pull request preview playground #666
Merged
mauteri
merged 70 commits into
GatherPress:main
from
carstingaxion:feature/pull-request-preview-playground
Jul 29, 2024
Merged
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
dca4ba3
NEW Action to prepare a .zip file with the current state of the plugin
carstingaxion 4ed15b3
Fix nested zips
carstingaxion dbef3f4
Exclude /src, /test & some files from being distributed
carstingaxion fc07a07
NEW step: Comment with playground link
carstingaxion ffd52ca
Re-Add file-extension
carstingaxion a70b584
Add debug logging
carstingaxion 29a6443
TRY TO FIX: "Unhandled error: HttpError: Resource not accessible by i…
carstingaxion 2fd282a
Trigger workflow only for playground-relevant files
carstingaxion 9cfaff3
Add info on pull_request_target
carstingaxion c141afd
Run again
carstingaxion 64d1a71
Run again
carstingaxion f4780c8
Fix filename
carstingaxion 198eec7
Keep readme on ditribution
carstingaxion 3ecf11b
TRY TO: comment on an PR
carstingaxion 149ea61
TRY TO: comment on an PR
carstingaxion e00862c
TRY TO: Set GITHUB_TOKEN explicit, to comment on a PR
carstingaxion a0202f0
Simplify job names
carstingaxion 0074760
Create blueprint on thefly
carstingaxion d270e31
Let steps depend on each other
carstingaxion fa33f32
TEST blueprint creation with hard-coded values
carstingaxion b08176f
Rename asset-name & Re-Run
carstingaxion 0782c88
Unset fixed PR number
carstingaxion 249c810
Match workflow name (with what is compared against at plugin-proxy.ph…
carstingaxion 34b3da3
TRY TO: Rename the workflow (because that is checked, not the job name
carstingaxion 593d043
Change start folder
carstingaxion 3ec5c29
Change start folder v2
carstingaxion 3e28868
Change start folder v3
carstingaxion d1f53f2
Change proxy URL
carstingaxion 7e6f6f0
NEW (WIP) CORS proxy for Github release artifacts
carstingaxion 6362da9
Exclude tooling from distribution
carstingaxion e7a4d34
Fix typo
carstingaxion 114df0e
Minor cleanup
carstingaxion 8ff151c
TEMP Disable commenting workflow
carstingaxion aeca6e1
Apply PR number to site title
carstingaxion e1e701b
Remove 'landingPage' to allow more flexible setting via 'url'
carstingaxion 8e6f9d2
DEBUG output
carstingaxion 40b5717
Minor fixes
carstingaxion 158cda1
Replace static with flexible values
carstingaxion 539482a
Show Admin Notice with PR & Link
carstingaxion 634d898
Add some slashes
carstingaxion 77a7edb
Fix typo in proxy URL
carstingaxion 7a42c65
TRY TO: Fix slashes after encoding
carstingaxion f07c1f0
TRY TO: Fix slashes after encoding (2)
carstingaxion 2803e3c
Cleanup
carstingaxion 2b1fdd4
Reset to static landingpage
carstingaxion 45f65c2
Remove admin_notice
carstingaxion b23432f
WIP Combining v1 & v2 workflows
carstingaxion 78f2e3e
WIP Combining v1 & v2 workflows (DEBUG)
carstingaxion 56d76e1
WIP Combining v1 & v2 workflows (DEBUG)
carstingaxion fdf31d1
Encode blueprint to be used as URL
carstingaxion 5bdb50a
Add download link to the comment
carstingaxion a44f283
Import GatherPress-demo-data
carstingaxion 5b36584
Cleanup (unused code)
carstingaxion ccef219
Rename workflow (and files)
carstingaxion ba7ba01
Make sure to have 'Content-Disposition' headers in place
carstingaxion 5740c1d
DRY out comment title
carstingaxion bf4f1b8
Create different playground preview links
carstingaxion 7ab25c9
Polishing the links as buttons
carstingaxion b3f7f90
TRY to fix md rendering
carstingaxion cb887f8
TRY to fix md rendering
carstingaxion e7cbb97
TRY to fix md rendering
carstingaxion 9ecc600
TRY to fix md rendering
carstingaxion 5042f3d
Exclude plugin-proxy from CS checks
carstingaxion 006a53c
TRY to fix md rendering
carstingaxion 51adbf7
TRY to fix md rendering
carstingaxion 0bcf173
Add PR number to link text (a11y)
carstingaxion 8ef56b8
TRY to fix md rendering
carstingaxion c303b63
Update doc-comments
carstingaxion 6b79b3d
Remove additional files from BUILD
carstingaxion 410c468
Merge branch 'main' into feature/pull-request-preview-playground
carstingaxion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
# ---------------------------------------------------------------------- | ||
# Proper MIME type for plugin-proxy.php | ||
# ---------------------------------------------------------------------- | ||
|
||
# # zip | ||
# AddType application/zip zip | ||
# AddType application/zip .zip | ||
|
||
|
||
<IfModule mod_headers.c> | ||
# THIS IS IMPORTANT | ||
# The missing piece I was hunting for 2 days. | ||
# | ||
# TODO # WEIRD | ||
# Having this here DOES WORK, | ||
# the exact same line DOES NOT WORK when placed in plugin-proxy.php. | ||
Header set Content-Disposition "attachment; filename=\"gatherpress-pr.zip\"" | ||
</IfModule> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
<?php | ||
/** | ||
* https://gatherpress.org/some-random-chars/plugin-proxy.php?org=GatherPress&repo=gatherpress&workflow=Build%20GatherPress%20Plugin%20Zip&artifact=gatherpress-pr&pr=666 | ||
* | ||
* | ||
* Based on: https://github.com/WordPress/wordpress-playground/blob/ca759ee3281837596bfdd9736bbea205104741b2/packages/playground/website/public/plugin-proxy.php | ||
*/ | ||
ini_set( 'display_errors', 0 ); | ||
|
||
putenv( 'GITHUB_TOKEN=___REPLACE_WITH_REAL_GITHUB_TOKEN___' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace Only in PRODUCTION on gatherpress.org, NOT here in the repo! |
||
|
||
class ApiException extends Exception { | ||
|
||
} | ||
class PluginDownloader { | ||
|
||
|
||
private $githubToken; | ||
|
||
public function __construct( $githubToken ) { | ||
$this->githubToken = $githubToken; | ||
} | ||
|
||
public function streamFromGithubPR( $organization, $repo, $pr, $workflow_name, $artifact_name ) { | ||
$prDetails = $this->gitHubRequest( "https://api.github.com/repos/$organization/$repo/pulls/$pr" )['body']; | ||
if ( ! $prDetails ) { | ||
throw new ApiException( 'invalid_pr_number' ); | ||
} | ||
$branchName = urlencode( $prDetails->head->ref ); | ||
$ciRuns = $this->gitHubRequest( "https://api.github.com/repos/$organization/$repo/actions/runs?branch=$branchName" )['body']; | ||
if ( ! $ciRuns ) { | ||
throw new ApiException( 'no_ci_runs' ); | ||
} | ||
|
||
$artifactsUrls = []; | ||
foreach ( $ciRuns->workflow_runs as $run ) { | ||
if ( $run->name === $workflow_name ) { | ||
$artifactsUrls[] = $run->artifacts_url; | ||
} | ||
} | ||
if ( ! $artifactsUrls ) { | ||
throw new ApiException( 'artifact_not_found' ); | ||
} | ||
|
||
foreach ( $artifactsUrls as $artifactsUrl ) { | ||
$zip_download_api_endpoint = $zip_url = null; | ||
|
||
$artifacts = $this->gitHubRequest( $artifactsUrl )['body']; | ||
if ( ! $artifacts ) { | ||
continue; | ||
} | ||
|
||
foreach ( $artifacts->artifacts as $artifact ) { | ||
if ( $artifact_name === $artifact->name ) { | ||
if ( $artifact->size_in_bytes < 3000 ) { | ||
throw new ApiException( 'artifact_invalid' ); | ||
} | ||
$zip_download_api_endpoint = $artifact->archive_download_url; | ||
break; | ||
} | ||
} | ||
if ( ! $zip_download_api_endpoint ) { | ||
continue; | ||
} | ||
|
||
/* | ||
* Short-circuit with HTTP 200 OK when we only want to | ||
* verify whether the CI artifact seems to exist but we | ||
* don't want to download it yet. | ||
*/ | ||
if ( array_key_exists( 'verify_only', $_GET ) ) { | ||
header( 'HTTP/1.1 200 OK' ); | ||
return; | ||
} | ||
|
||
$allowed_headers = array( | ||
// 'accept-ranges', | ||
'content-disposition', | ||
'content-length', | ||
// 'content-type', | ||
'x-frame-options', | ||
'last-modified', | ||
'etag', | ||
'date', | ||
'age', | ||
'vary', | ||
'cache-Control', | ||
); | ||
$artifact_res = $this->gitHubRequest( $zip_download_api_endpoint, false, false ); | ||
|
||
// die(var_export($artifact_res['headers'],true)); | ||
ob_end_flush(); | ||
flush(); | ||
|
||
// The API endpoint returns the actual artifact URL as a 302 Location header. | ||
foreach ( $artifact_res['headers'] as $header_line ) { | ||
$header_name = strtolower( substr( $header_line, 0, strpos( $header_line, ':' ) ) ); | ||
$header_value = trim( substr( $header_line, 1 + strpos( $header_line, ':' ) ) ); | ||
if ( $header_name === 'location' ) { | ||
streamHttpResponse( | ||
$header_value, | ||
'GET', | ||
[], | ||
null, | ||
$allowed_headers, | ||
[ | ||
'Content-Type: application/zip', | ||
// TODO // WEIRD | ||
// Having this here DOES NOT WORK, | ||
// the exact same line needs to be placed in .htaccess. | ||
// 'Content-Disposition: attachment; filename="gatherpress-pr.zip"', | ||
] | ||
); | ||
die(); | ||
} | ||
} | ||
throw new ApiException( 'artifact_redirect_not_present' ); | ||
} | ||
if ( ! $artifacts ) { | ||
throw new ApiException( 'artifact_not_available' ); | ||
} | ||
if ( ! $zip_download_api_endpoint ) { | ||
throw new ApiException( 'artifact_not_available' ); | ||
} | ||
if ( ! $zip_url ) { | ||
throw new ApiException( 'artifact_not_available' ); | ||
} | ||
} | ||
|
||
protected function gitHubRequest( $url, $decode = true, $follow_location = true ) { | ||
$headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'; | ||
$headers[] = 'Authorization: Bearer ' . $this->githubToken; | ||
$context = stream_context_create( | ||
[ | ||
'http' => [ | ||
'method' => 'GET', | ||
'header' => implode( "\r\n", $headers ), | ||
'follow_location' => $follow_location, | ||
], | ||
] | ||
); | ||
$response = file_get_contents( $url, false, $context ); | ||
if ( $response === false ) { | ||
throw new ApiException( 'Request failed' ); | ||
} | ||
// Find the last index of "HTTP/1.1 200 OK" in $http_response_header array | ||
for ( $i = count( $http_response_header ) - 1; $i >= 0; $i-- ) { | ||
if ( substr( $http_response_header[ $i ], 0, 12 ) === 'HTTP/1.1 200' ) { | ||
break; | ||
} | ||
} | ||
$headers = array_map( 'trim', array_slice( $http_response_header, $i + 1 ) ); | ||
return [ | ||
'body' => $decode ? json_decode( $response ) : $response, | ||
'headers' => $headers, | ||
]; | ||
} | ||
} | ||
|
||
function streamHttpResponse( $url, $request_method = 'GET', $request_headers = [], $request_body = null, $allowed_response_headers = [], $default_response_headers = [] ) { | ||
$ch = curl_init( $url ); | ||
curl_setopt_array( | ||
$ch, | ||
[ | ||
CURLOPT_RETURNTRANSFER => true, | ||
CURLOPT_CONNECTTIMEOUT => 30, | ||
CURLOPT_FAILONERROR => true, | ||
CURLOPT_FOLLOWLOCATION => true, | ||
] | ||
); | ||
|
||
if ( $request_method === 'POST' ) { | ||
curl_setopt( $ch, CURLOPT_POST, true ); | ||
curl_setopt( $ch, CURLOPT_POSTFIELDS, $request_body ); | ||
} elseif ( $request_method === 'HEAD' ) { | ||
curl_setopt( $ch, CURLOPT_NOBODY, true ); | ||
} | ||
|
||
if ( count( $request_headers ) ) { | ||
curl_setopt( $ch, CURLOPT_HTTPHEADER, $request_headers ); | ||
} | ||
|
||
$seen_headers = []; | ||
curl_setopt( | ||
$ch, | ||
CURLOPT_HEADERFUNCTION, | ||
function ( $curl, $header_line ) use ( $seen_headers, $allowed_response_headers ) { | ||
if ( strpos( $header_line, ':' ) === false ) { | ||
return strlen( $header_line ); | ||
} | ||
$header_name = strtolower( substr( $header_line, 0, strpos( $header_line, ':' ) ) ); | ||
$seen_headers[ $header_name ] = true; | ||
$illegal_headers = [ 'transfer-encoding' ]; | ||
$header_allowed = ( | ||
null === $allowed_response_headers || in_array( $header_name, $allowed_response_headers ) | ||
) && ! in_array( $header_name, $illegal_headers ); | ||
if ( $header_allowed ) { | ||
header( $header_line ); | ||
} | ||
return strlen( $header_line ); | ||
} | ||
); | ||
$extra_headers_sent = false; | ||
curl_setopt( | ||
$ch, | ||
CURLOPT_WRITEFUNCTION, | ||
function ( $curl, $body ) use ( &$extra_headers_sent, $default_response_headers ) { | ||
if ( ! $extra_headers_sent ) { | ||
foreach ( $default_response_headers as $header_line ) { | ||
$header_name = strtolower( substr( $header_line, 0, strpos( $header_line, ':' ) ) ); | ||
if ( ! isset( $seen_headers[ $header_name ] ) ) { | ||
header( $header_line ); | ||
} | ||
} | ||
$extra_headers_sent = true; | ||
} | ||
echo $body; | ||
flush(); | ||
return strlen( $body ); | ||
} | ||
); | ||
curl_exec( $ch ); | ||
$info = curl_getinfo( $ch ); | ||
curl_close( $ch ); | ||
return $info; | ||
} | ||
|
||
$downloader = new PluginDownloader( | ||
getenv( 'GITHUB_TOKEN' ) | ||
); | ||
|
||
// Serve the request: | ||
if ( ! array_key_exists( 'url', $_GET ) ) { | ||
header( 'Access-Control-Allow-Origin: *' ); | ||
} | ||
$pluginResponse; | ||
try { | ||
if ( isset( $_GET['org'] ) && isset( $_GET['repo'] ) && isset( $_GET['workflow'] ) && isset( $_GET['pr'] ) && isset( $_GET['artifact'] ) ) { | ||
$allowedInputs = [ | ||
[ | ||
'org' => 'GatherPress', | ||
'repo' => 'gatherpress', | ||
'workflow' => 'Playground Preview', | ||
'artifact' => '#gatherpress-pr#', | ||
], | ||
]; | ||
$allowed = false; | ||
foreach ( $allowedInputs as $allowedInput ) { | ||
if ( | ||
$_GET['org'] === $allowedInput['org'] && | ||
$_GET['repo'] === $allowedInput['repo'] && | ||
$_GET['workflow'] === $allowedInput['workflow'] && | ||
preg_match( $allowedInput['artifact'], $_GET['artifact'] ) | ||
) { | ||
$allowed = true; | ||
break; | ||
} | ||
} | ||
if ( ! $allowed ) { | ||
header( 'HTTP/1.1 400 Invalid request' ); | ||
die( 'Invalid request: The specified URL is not allowed.' ); | ||
} | ||
$downloader->streamFromGithubPR( | ||
$_GET['org'], | ||
$_GET['repo'], | ||
$_GET['pr'], | ||
$_GET['workflow'], | ||
$_GET['artifact'] | ||
); | ||
|
||
// ** | ||
// * Pass through the request headers we got from WordPress via fetch(), | ||
// * then filter out: | ||
// * | ||
// * * The browser-specific headers | ||
// * * Headers related to security to avoid leaking any auth information | ||
// * | ||
// * ...and pass the rest to the proxied request. | ||
// * | ||
// * @return array | ||
// */ | ||
// function get_request_headers() | ||
// { | ||
// $headers = []; | ||
// foreach ($_SERVER as $name => $value) { | ||
// if (substr($name, 0, 5) !== 'HTTP_') { | ||
// continue; | ||
// } | ||
// $name = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($name, 5))))); | ||
// $lcname = strtolower($name); | ||
// if ( | ||
// $lcname === 'authorization' | ||
// || $lcname === 'cookie' | ||
// || $lcname === 'host' | ||
// || $lcname === 'origin' | ||
// || $lcname === 'referer' | ||
// || 0 === strpos($lcname, 'sec-') | ||
// ) { | ||
// continue; | ||
// } | ||
// $headers[$name] = $value; | ||
// } | ||
// return $headers; | ||
// } | ||
|
||
// streamHttpResponse( | ||
// $url, | ||
// $_SERVER['REQUEST_METHOD'], | ||
// get_request_headers(), | ||
// file_get_contents('php://input'), | ||
// null | ||
// ); | ||
} else { | ||
throw new ApiException( 'Invalid query parameters' ); | ||
} | ||
} catch ( ApiException $e ) { | ||
header( 'HTTP/1.1 400 Invalid request' ); | ||
if ( ! headers_sent() ) { | ||
header( 'Content-Type: application/json' ); | ||
} | ||
die( json_encode( [ 'error' => $e->getMessage() ] ) ); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be uploaded to
gatherpress.org
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MervinHernandez you'll need to work with Carstin on this to get on gatherpress.org