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 support for the new infrastructure #6

Merged
merged 7 commits into from
Oct 28, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: ["7.4", "8.0", "8.1", "8.2"]
php-version: ["8.0", "8.1", "8.2", "8.3"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -45,7 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: ["7.4", "8.0", "8.1", "8.2"]
php-version: ["8.0", "8.1", "8.2", "8.3"]
steps:
- uses: actions/checkout@v3
- name: Installing PHP ${{ matrix.php-version }}
Expand Down
96 changes: 66 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,51 @@ composer require vanilla/garden-sites

## Usage

The entrypoint to usage of this library is through either the `Garden\Sites\LocalSiteProvider` or the `Garden\Sites\OrchSiteProvider`.
The entrypoint to usage of this library is through either the `Garden\Sites\LocalSiteProvider`, `Garden\Sites\DashboardSiteProvider`, `Garden\Sites\OrchSiteProvider`.

### LocalSiteProvider
### Configuring in Laravel

To configure this library in Laravel you need to setup the following 2 things.

**conf/orch.php**

```php
use Garden\Sites\LaravelProviderFactory;

// This will inject your env variables into laravel config.
return LaravelProviderFactory::createLaravelConfigFromEnv("env");
```

**Usage in the app**

```php
use Garden\Sites\Local\LocalSiteProvider;
use Garden\Sites\LaravelProviderFactory;use Garden\Sites\SiteProvider;

/** @var SiteProvider $provider */
$provider = LaravelProviderFactory::providerFromLaravelConfig([\Config::class, 'get'])

/**
* Site providers do various caching of results. By default an in-memory cache is used, but especially with an orch-client
* it is recommended to configure a persistent cache like memcached or redis.
* Caches must implement {@link CacheInterface}
*/

$cache = new RedisAdapter(/** Configuration here. */);
// or
$cache = new MemcachedAdapter(/** Configuration here. */);

$siteProvider->setCache($cache);
```

### LocalSiteProvider

This provider reads site configurations from a local directory. The provider is configured with a path to the directory containing the site configurations.

$provider = new LocalSiteProvider("/path/to/site/configs");
**.env**

```env
ORCH_TYPE="local"
ORCH_LOCAL_DIRECTORY_PATH="/path/to/site/configs"
```

Notably the path to the site configs must be a readable directory to the PHP process.
Expand All @@ -44,38 +81,37 @@ Cluster configurations may be added into the configs `/clusters` directory and a

The orch site provider loads sites and clusters from a remote orchestration http server. Sites, clusters, and configs are cached for a 1 minute period.

```php
use Garden\Sites\Clients\OrchHttpClient;
use Garden\Sites\Orch\OrchSiteProvider;
use Garden\Sites\Orch\OrchCluster;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Contracts\Cache\CacheInterface;

$orchHttpClient = new OrchHttpClient("https://orch.vanilla.localhost", "access-token-here");
$siteProvider = new OrchSiteProvider($orchHttpClient, [OrchCluster::REGION_AMS1_PROD1]);

// It is highly recommended to set a user-agent for network requests.
$siteProvider->setUserAgent("my-service:1.0");
**.env**

```env
ORCH_TYPE="orchestration"
ORCH_BASE_URL="https://orchestration.vanilladev.com"
ORCH_TOKEN="SECRET_HERE"
# Optional hostname to force for orchestration (Force Proxy from localhost)
ORCH_HOSTNAME="ORCH_HOSTNAME";
# CSV of region IDs to accept sites from.
ORCH_REGION_IDS="yul1-prod1,sjc1-prod1";
ORCH_USER_AGENT="my-service";
```

/**
* Site providers do various caching of results. By default an in-memory cache is used, but especially with an orch-client
* it is recommended to configure a persistent cache like memcached or redis.
* Caches must implement {@link CacheInterface}
*/
### DashboardSiteProvider

$cache = new RedisAdapter(/** Configuration here. */);
// or
$cache = new MemcachedAdapter(/** Configuration here. */);
The orch site provider loads sites and clusters from a remote management-dashboard http server. Sites, clusters, and configs are cached for a 1 minute period.

$siteProvider->setCache($cache);
**.env**

# Region can be changed later
$siteProvider->setRegionIDs([OrchCluster::REGION_YUL1_PROD1, OrchCluster::REGION_AMS1_PROD1]);
```env
ORCH_TYPE="dashboard"
ORCH_BASE_URL="https://management-dashboard.vanilladev.com"
# JWT secret for management dashboard
ORCH_TOKEN="SECRET_HERE"
# Optional hostname to force for management dashboard (Force Proxy from localhost)
ORCH_HOSTNAME="ORCH_HOSTNAME";
# CSV of region IDs to accept sites from.
ORCH_REGION_IDS="yul1-prod1,sjc1-prod1";
ORCH_USER_AGENT="my-service";
```

The orchestration provider needs to be configured with an authenticated `OrchHttpClient` and a region/network to load sites from.

### Using site providers

Both `OrchSiteProvider` and `LocalSiteProvider` extend from `SiteProvider` and implement similar functionality.
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
],
"require": {
"vanilla/garden-http": "^2.8",
"psr/http-message": "^1.0.1",
"ext-json": "*",
"php": ">=7.4",
"php": ">=8.0",
"vanilla/garden-utils": "^1.0.1",
"symfony/cache-contracts": "^2.5"
"symfony/cache-contracts": "^2.5",
"firebase/php-jwt": "^6.10"
}
}
70 changes: 70 additions & 0 deletions src/Clients/DashboardHttpClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/**
* @copyright 2009-2024 Vanilla Forums Inc.
* @license Proprietary
*/

namespace Garden\Sites\Clients;

use Garden\Http\HttpClient;
use Garden\Http\HttpRequest;
use Garden\Http\HttpResponse;
use Garden\Sites\Utils\HttpUtils;

/**
* HTTP client for the management API.
*/
class DashboardHttpClient extends HttpClient
{
private string $userAgent = "garden-sites";

/**
* @param string $orchBaseUrl
* @param string $secret
* @param string|null $forcedHostname Use to force a hostname on to the HTTP client.
*/
public function __construct(string $orchBaseUrl, string $secret, ?string $forcedHostname = null)
{
parent::__construct($orchBaseUrl);
$this->setDefaultHeader("content-type", "application/json");
$this->setThrowExceptions(true);

if ($forcedHostname !== null) {
HttpUtils::forceForceHostname($this, $forcedHostname);
}

$this->addMiddleware(function (HttpRequest $request, callable $next) use ($secret): HttpResponse {
// Generate a JWT.
$jwt = \Firebase\JWT\JWT::encode(
[
"iss" => $this->userAgent,
"iat" => time(),
"exp" => time() + 60,
],
$secret,
"HS512",
);

$token = "sys:{$this->userAgent}:$jwt";
// Set the Authorization header.
$request->setHeader("Authorization", "Bearer $token");

$response = $next($request);
return $response;
});
}

/**
* Set the user agent for the orchestration client to use.
*
* @param string $userAgent
*
* @return $this For method chaining.
*/
public function setUserAgent(string $userAgent): DashboardHttpClient
{
$this->userAgent = $userAgent;
$this->setDefaultHeader("User-Agent", $userAgent);
return $this;
}
}
7 changes: 6 additions & 1 deletion src/Clients/OrchClusterClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ class OrchClusterClient extends HttpClient
{
public function __construct(OrchCluster $cluster)
{
parent::__construct("https://data.{$cluster->getClusterID()}.vanilladev.com");
$clusterId = $cluster->getClusterID();
$url = preg_match("/^cl[123456]00[0-9]{2}$/", $clusterId)
? "https://data.{$clusterId}.vanilladev.com"
: "http://data.{$clusterId}.service.consul";

parent::__construct($url);
$this->setDefaultHeaders([
"X-Access-Token" => $cluster->getSecret(),
"Content-Type" => "application/json",
Expand Down
44 changes: 6 additions & 38 deletions src/Clients/OrchHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Garden\Sites\Clients;

use Garden\Http\HttpClient;
use Garden\Sites\Utils\HttpUtils;

/**
* HTTP Client for communication with vanilla orchestration.
Expand All @@ -17,12 +18,15 @@ class OrchHttpClient extends HttpClient
* @param string $orchBaseUrl
* @param string $accessToken
*/
public function __construct(string $orchBaseUrl, string $accessToken)
public function __construct(string $orchBaseUrl, string $accessToken, string|null $forcedHostname = null)
{
parent::__construct($orchBaseUrl);
$this->setThrowExceptions(true);
$this->setDefaultHeader("content-type", "application/json");
$this->setDefaultHeader("X-Access-Token", $accessToken);
if ($forcedHostname !== null) {
$this->forceIpAddress($forcedHostname);
}
}

/**
Expand All @@ -35,13 +39,7 @@ public function __construct(string $orchBaseUrl, string $accessToken)
*/
public function forceIpAddress(string $ipAddress): OrchHttpClient
{
$baseUrl = $this->getBaseUrl();
$parsed = parse_url($baseUrl);
$hostname = $parsed["host"] ?? "";
$parsed["host"] = $ipAddress;
$newBaseUrl = $this->unparseUrl($parsed);
$this->setBaseUrl($newBaseUrl);
$this->setDefaultHeader("Host", $hostname);
HttpUtils::forceForceHostname($this, $ipAddress);
return $this;
}

Expand All @@ -57,34 +55,4 @@ public function setUserAgent(string $userAgent): OrchHttpClient
$this->setDefaultHeader("User-Agent", $userAgent);
return $this;
}

/**
* Inverse of `parse_url()`.
*
* @param array $parsedUrl Return of {@link parse_url()}
*
* @return string
*/
private function unparseUrl(array $parsedUrl): string
{
$scheme = isset($parsedUrl["scheme"]) ? $parsedUrl["scheme"] . "://" : "";

$host = $parsedUrl["host"] ?? "";

$port = isset($parsedUrl["port"]) ? ":" . $parsedUrl["port"] : "";

$user = $parsedUrl["user"] ?? "";

$pass = isset($parsedUrl["pass"]) ? ":" . $parsedUrl["pass"] : "";

$pass = $user || $pass ? "$pass@" : "";

$path = $parsedUrl["path"] ?? "";

$query = isset($parsedUrl["query"]) ? "?" . $parsedUrl["query"] : "";

$fragment = isset($parsedUrl["fragment"]) ? "#" . $parsedUrl["fragment"] : "";

return "$scheme$user$pass$host$port$path$query$fragment";
}
}
27 changes: 27 additions & 0 deletions src/Dashboard/DashboardCluster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* @copyright 2009-2023 Vanilla Forums Inc.
* @license Proprietary
*/

namespace Garden\Sites\Dashboard;

use Garden\Sites\Clients\OrchClusterClient;
use Garden\Sites\Cluster;

/**
* Implementation of a cluster loaded from orchestration.
*/
class DashboardCluster extends Cluster
{
/**
* Constructor.
*
* @param string $clusterID
* @param string $regionID
*/
public function __construct(string $clusterID, string $regionID)
{
parent::__construct($clusterID, $regionID);
}
}
Loading
Loading