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

Add downloads for nightlies and pull requests #351

Merged
merged 5 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ APP_SECRET=c55366de7d2b4845909b31d21b076b40
###< symfony/framework-bundle ###

###> knplabs/github-api ###
GITHUB_AUTH_METHOD=http_password
GITHUB_AUTH_METHOD=client_id_header
GITHUB_USERNAME=username
GITHUB_SECRET=password_or_token
###< knplabs/github-api ###
5 changes: 3 additions & 2 deletions config/packages/cache.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ framework:
#app: cache.adapter.apcu

# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null
pools:
github_api.cache:
adapter: cache.adapter.filesystem
2 changes: 2 additions & 0 deletions config/packages/github_api.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
services:
Github\Client:
class: LMMS\GithubDownloadClient
arguments:
- '@Github\HttpClient\Builder'
calls:
- ['authenticate', ['%env(GITHUB_USERNAME)%', '%env(GITHUB_SECRET)%', '%env(GITHUB_AUTH_METHOD)%']]
- ['addCache', ['@github_api.cache', {default_ttl: 1800}]]

Github\HttpClient\Builder:
arguments:
Expand Down
8 changes: 8 additions & 0 deletions config/routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ download:
path: /download
controller: App\Controller\DownloadController::page

download_pr:
path: /download/pull-request/{id}
controller: App\Controller\DownloadController::pull_request

download_artifact:
path: /download/artifact/{id}
controller: App\Controller\DownloadController::artifact

portal_pages:
path: /{page}
controller: App\Controller\PortalController::page
Expand Down
4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
string $owner: LMMS
string $repo: lmms
string $workflow: build.yml

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Expand Down
127 changes: 127 additions & 0 deletions lib/Artifacts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
namespace LMMS;

use Github\Client;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class Artifacts
{
public function __construct(
private Client $client,
private string $owner,
private string $repo,
private string $workflow,
private UrlGeneratorInterface $router
)
{ }

public function getForBranch(string $branch): array
{
// Get the latest successful runs of the target workflow
$runs = $this->client->repo()->workflowRuns()->listRuns($this->owner, $this->repo, $this->workflow, [
'event' => 'push',
'branch' => $branch,
'status' => 'success',
'per_page' => 1
]);
if ($runs['total_count'] > 0) {
// Get all artifacts of that run
$runId = $runs['workflow_runs'][0]['id'];
$artifacts = $this->client->repo()->artifacts()->runArtifacts($this->owner, $this->repo, $runId);

$validArtifacts = array_filter($artifacts['artifacts'], function ($artifact) {
return !$artifact['expired'];
});
return $this->mapBranchAssetsFromJson($validArtifacts);
}
return [];
}

public function getForPullRequest(int $id): array
{
// Get the most recent commit in the pull request
$pr = $this->client->pr()->show($this->owner, $this->repo, $id);
$ref = $pr['head']['sha'];

// Get all check runs for that commit
$checks = $this->client->repo()->checkRuns()->allForReference($this->owner, $this->repo, $ref);

// Find a check run corresponding to the GitHub Actions build workflow
foreach ($checks['check_runs'] as $run) {
if ($run['app']['slug'] === 'github-actions' && $run['name'] === 'linux') {
$jobId = $run['id'];
}
}
if (!isset($jobId)) { return []; }

// Get the GitHub Actions workflow run corresponding to that check run
$job = $this->client->repo()->workflowJobs()->show($this->owner, $this->repo, $jobId);
$runId = $job['run_id'];

// Get all artifacts of that workflow run
$artifacts = $this->client->repo()->artifacts()->runArtifacts($this->owner, $this->repo, $runId);

$validArtifacts = array_filter($artifacts['artifacts'], function ($artifact) {
return !$artifact['expired'];
});
$description = '## ' . $pr['title'] . "\n" . $pr['body'];
return $this->mapPullRequestAssetsFromJson($validArtifacts, $id, $description);
}

public function getArtifactDownloadUrl(int $artifactId): string
{
return (string) $this->client->repo()->artifacts()->download($this->owner, $this->repo, $artifactId);
}

private function mapBranchAssetsFromJson(array $json): array
{
return array_map(function (array $artifact) {
return new Asset(
platform: self::platformFromArtifactName($artifact['name']),
platformName: self::platformNameFromArtifactName($artifact['name']),
releaseName: 'g' . substr($artifact['workflow_run']['head_sha'], 0, 9),
downloadUrl: $this->router->generate('download_artifact', ['id' => $artifact['id']]),
description: null,
gitRef: $artifact['workflow_run']['head_sha'],
date: $artifact['created_at']
);
}, $json);
}

private function mapPullRequestAssetsFromJson(array $json, string $pr, string $description): array
{
return array_map(function (array $artifact) use ($pr, $description) {
return new Asset(
platform: self::platformFromArtifactName($artifact['name']),
platformName: self::platformNameFromArtifactName($artifact['name']),
releaseName: '#' . $pr . '@' . substr($artifact['workflow_run']['head_sha'], 0, 9),
downloadUrl: $this->router->generate('download_artifact', ['id' => $artifact['id']]),
description: $description,
gitRef: $artifact['workflow_run']['head_sha'],
date: $artifact['created_at']
);
}, $json);
}

private static function platformFromArtifactName(string $artifactName): Platform
{
switch ($artifactName) {
case 'linux': return Platform::Linux;
case 'macos': return Platform::MacOS;
case 'mingw32': return Platform::Windows;
case 'mingw64': return Platform::Windows;
default: return Platform::Unknown;
}
}

private static function platformNameFromArtifactName(string $artifactName): string
{
switch($artifactName) {
case 'linux': return 'Linux 64-bit';
case 'macos': return 'macOS 10.15+';
case 'mingw32': return 'Windows 32-bit';
case 'mingw64': return 'Windows 64-bit';
default: return 'Unknown (' . $artifactName . ')';
}
}
}
51 changes: 51 additions & 0 deletions lib/Asset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
namespace LMMS;

class Asset
{
public function __construct(
private Platform $platform,
private string $platformName,
private string $releaseName,
private string $downloadUrl,
private ?string $description,
private string $gitRef,
private string $date
)
{ }

public function getPlatform(): Platform
{
return $this->platform;
}

public function getPlatformName(): string
{
return $this->platformName;
}

public function getReleaseName(): string
{
return $this->releaseName;
}

public function getDownloadUrl(): string
{
return $this->downloadUrl;
}

public function getDescription(): ?string
{
return $this->description;
}

public function getGitRef(): string
{
return $this->gitRef;
}

public function getDate(): string
{
return $this->date;
}
}
11 changes: 3 additions & 8 deletions lib/GitHubMarkdownEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@

class GitHubMarkdownEngine implements MarkdownEngineInterface
{
public function __construct()
public function __construct(private \Github\Client $client)
{
$this->cache = new FilesystemAdapter();
$this->client = new \Github\Client();
$this->client->addCache($this->cache, [
'default_ttl' => 7200
]);
}

public function transform($content): string
Expand All @@ -26,9 +22,9 @@ public function transform($content): string
if (substr_compare('|GH|', $content, 0, 4) === 0) {
$content = substr($content, 4, strlen($content));
try {
$response = $this->client->api('markdown')->render($content, 'gfm', 'LMMS/lmms');
$response = $this->client->markdown()->render($content, 'gfm', 'LMMS/lmms');
return $response;
} catch (Exception $e) {
} catch (\Exception $e) {
error_log($e);
return MarkdownExtra::defaultTransform($content);
}
Expand All @@ -43,6 +39,5 @@ public function getName(): string
return 'LMMS\GitHubMarkdown';
}

private $client;
private $cache;
}
42 changes: 42 additions & 0 deletions lib/GithubDownloadClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
namespace LMMS;

use Github\Client;
use Github\HttpClient\Builder;
use Http\Client\Common\Plugin;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Promise\Promise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Extends the KNP Labs GitHub Client to provide URLs for artifact downloads,
* rather than the artifacts themselves.
*/
class GithubDownloadClient extends Client
{
public function __construct(Builder $httpClientBuilder = null, $apiVersion = null, $enterpriseUrl = null)
{
parent::__construct($httpClientBuilder, $apiVersion, $enterpriseUrl);
$this->getHttpClientBuilder()->addPlugin(new class implements Plugin {
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
{
return $next($request)->then(function(ResponseInterface $response): ResponseInterface {
if ($response->getStatusCode() === 302) {
$location = $response->getHeader('Location')[0];
if (str_starts_with($location, 'https://pipelines.actions.githubusercontent.com')) {
$body = Psr17FactoryDiscovery::findStreamFactory()->createStream($location);
$body->rewind();
return $response
->withStatus(200)
->withHeader('Content-Type', 'text/plain')
->withoutHeader('Location')
->withBody($body);
}
}
return $response;
});
}
});
}
}
10 changes: 10 additions & 0 deletions lib/Platform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
namespace LMMS;

enum Platform
{
case Unknown;
case Linux;
case MacOS;
case Windows;
}
Loading