Skip to content
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
Merged
Show file tree
Hide file tree
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 May 31, 2024
4ed15b3
Fix nested zips
carstingaxion May 31, 2024
dbef3f4
Exclude /src, /test & some files from being distributed
carstingaxion May 31, 2024
fc07a07
NEW step: Comment with playground link
carstingaxion May 31, 2024
ffd52ca
Re-Add file-extension
carstingaxion May 31, 2024
a70b584
Add debug logging
carstingaxion May 31, 2024
29a6443
TRY TO FIX: "Unhandled error: HttpError: Resource not accessible by i…
carstingaxion May 31, 2024
2fd282a
Trigger workflow only for playground-relevant files
carstingaxion May 31, 2024
9cfaff3
Add info on pull_request_target
carstingaxion May 31, 2024
c141afd
Run again
carstingaxion May 31, 2024
64d1a71
Run again
carstingaxion May 31, 2024
f4780c8
Fix filename
carstingaxion May 31, 2024
198eec7
Keep readme on ditribution
carstingaxion Jun 1, 2024
3ecf11b
TRY TO: comment on an PR
carstingaxion Jun 1, 2024
149ea61
TRY TO: comment on an PR
carstingaxion Jun 1, 2024
e00862c
TRY TO: Set GITHUB_TOKEN explicit, to comment on a PR
carstingaxion Jun 1, 2024
a0202f0
Simplify job names
carstingaxion Jun 1, 2024
0074760
Create blueprint on thefly
carstingaxion Jun 1, 2024
d270e31
Let steps depend on each other
carstingaxion Jun 1, 2024
fa33f32
TEST blueprint creation with hard-coded values
carstingaxion Jun 1, 2024
b08176f
Rename asset-name & Re-Run
carstingaxion Jun 1, 2024
0782c88
Unset fixed PR number
carstingaxion Jun 1, 2024
249c810
Match workflow name (with what is compared against at plugin-proxy.ph…
carstingaxion Jun 1, 2024
34b3da3
TRY TO: Rename the workflow (because that is checked, not the job name
carstingaxion Jun 1, 2024
593d043
Change start folder
carstingaxion Jun 1, 2024
3ec5c29
Change start folder v2
carstingaxion Jun 1, 2024
3e28868
Change start folder v3
carstingaxion Jun 1, 2024
d1f53f2
Change proxy URL
carstingaxion Jun 2, 2024
7e6f6f0
NEW (WIP) CORS proxy for Github release artifacts
carstingaxion Jun 2, 2024
6362da9
Exclude tooling from distribution
carstingaxion Jun 2, 2024
e7a4d34
Fix typo
carstingaxion Jun 2, 2024
114df0e
Minor cleanup
carstingaxion Jun 2, 2024
8ff151c
TEMP Disable commenting workflow
carstingaxion Jun 2, 2024
aeca6e1
Apply PR number to site title
carstingaxion Jun 2, 2024
e1e701b
Remove 'landingPage' to allow more flexible setting via 'url'
carstingaxion Jun 2, 2024
8e6f9d2
DEBUG output
carstingaxion Jun 2, 2024
40b5717
Minor fixes
carstingaxion Jun 2, 2024
158cda1
Replace static with flexible values
carstingaxion Jun 2, 2024
539482a
Show Admin Notice with PR & Link
carstingaxion Jun 2, 2024
634d898
Add some slashes
carstingaxion Jun 2, 2024
77a7edb
Fix typo in proxy URL
carstingaxion Jun 2, 2024
7a42c65
TRY TO: Fix slashes after encoding
carstingaxion Jun 2, 2024
f07c1f0
TRY TO: Fix slashes after encoding (2)
carstingaxion Jun 2, 2024
2803e3c
Cleanup
carstingaxion Jun 2, 2024
2b1fdd4
Reset to static landingpage
carstingaxion Jun 2, 2024
45f65c2
Remove admin_notice
carstingaxion Jun 3, 2024
b23432f
WIP Combining v1 & v2 workflows
carstingaxion Jul 28, 2024
78f2e3e
WIP Combining v1 & v2 workflows (DEBUG)
carstingaxion Jul 28, 2024
56d76e1
WIP Combining v1 & v2 workflows (DEBUG)
carstingaxion Jul 28, 2024
fdf31d1
Encode blueprint to be used as URL
carstingaxion Jul 28, 2024
5bdb50a
Add download link to the comment
carstingaxion Jul 28, 2024
a44f283
Import GatherPress-demo-data
carstingaxion Jul 28, 2024
5b36584
Cleanup (unused code)
carstingaxion Jul 28, 2024
ccef219
Rename workflow (and files)
carstingaxion Jul 28, 2024
ba7ba01
Make sure to have 'Content-Disposition' headers in place
carstingaxion Jul 28, 2024
5740c1d
DRY out comment title
carstingaxion Jul 28, 2024
bf4f1b8
Create different playground preview links
carstingaxion Jul 28, 2024
7ab25c9
Polishing the links as buttons
carstingaxion Jul 28, 2024
b3f7f90
TRY to fix md rendering
carstingaxion Jul 28, 2024
cb887f8
TRY to fix md rendering
carstingaxion Jul 28, 2024
e7cbb97
TRY to fix md rendering
carstingaxion Jul 28, 2024
9ecc600
TRY to fix md rendering
carstingaxion Jul 28, 2024
5042f3d
Exclude plugin-proxy from CS checks
carstingaxion Jul 28, 2024
006a53c
TRY to fix md rendering
carstingaxion Jul 28, 2024
51adbf7
TRY to fix md rendering
carstingaxion Jul 28, 2024
0bcf173
Add PR number to link text (a11y)
carstingaxion Jul 28, 2024
8ef56b8
TRY to fix md rendering
carstingaxion Jul 28, 2024
c303b63
Update doc-comments
carstingaxion Jul 28, 2024
6b79b3d
Remove additional files from BUILD
carstingaxion Jul 29, 2024
410c468
Merge branch 'main' into feature/pull-request-preview-playground
carstingaxion Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ wp-cli.local.yml
yarn.lock

# Dot Files and Directories
.gatherpress-org
.git
.github
.wordpress-org
Expand All @@ -45,6 +46,7 @@ Thumbs.db
.circleci/config.yml
node_modules
vendor
src

# Miscellaneous and SQL Archives
behat.yml
Expand All @@ -53,6 +55,7 @@ bin
multisite.xml
multisite.xml.dist
tests
*.mp4
*.sql
*.tar.gz
*.zip
*.zip
19 changes: 19 additions & 0 deletions .gatherpress-org/playground-preview/.htaccess
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>
322 changes: 322 additions & 0 deletions .gatherpress-org/playground-preview/plugin-proxy.php
Copy link
Collaborator Author

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

└── playground-preview
    ├── .htaccess
    └── plugin-proxy.php

Copy link
Contributor

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

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___' );
Copy link
Collaborator Author

@carstingaxion carstingaxion Jul 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace ___REPLACE_WITH_REAL_GITHUB_TOKEN___ with a working PAT (to comment on PRs).

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() ] ) );
}
Loading
Loading