Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Commit

Permalink
Merge pull request #54 from serato/local-file-path
Browse files Browse the repository at this point in the history
Adds local file path option to to Serato\SwsApp\ClientApplication\DataLoader
  • Loading branch information
chris-die authored Feb 20, 2019
2 parents 1b90738 + 474913b commit 8e885ff
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 65 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"files.exclude": {
"vendor": true
"vendor": true,
"**/docs": true
}
}
64 changes: 50 additions & 14 deletions src/ClientApplication/Cli/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,26 @@ abstract class AbstractCommand extends Command
public const OPTION_APP_NAME = 'app-name';
public const OPTION_ENVIRONMENT = 'env';
public const OPTION_IGNORE_CACHE = 'ignore-cache';
public const OPTION_LOCAL_DIRECTORY_PATH = 'local-dir-path';

/** @var string */
private $env;

/** @var AwsSdk */
private $awsSdk;

/** @var CacheItemPoolInterface */
private $psrCache;

/** @var boolean */
private $useCache = true;

/** @var DataLoader */
private $dataLoader;

/** @var string */
private $localDirPath;

/**
* Constructs the command
*
Expand All @@ -40,7 +50,8 @@ public function __construct(string $env, AwsSdk $awsSdk, CacheItemPoolInterface
{
parent::__construct();
$this->env = $env;
$this->dataLoader = new DataLoader($env, $awsSdk, $psrCache);
$this->awsSdk = $awsSdk;
$this->psrCache = $psrCache;
}

/**
Expand All @@ -53,28 +64,42 @@ protected function configure()
self::OPTION_ENVIRONMENT,
null,
InputOption::VALUE_REQUIRED,
"Application environment. One of `dev`, `test` or `production`.\n\n" .
"Application environment. One of `dev`, `test` or `production`. " .
"Overrides the value extracted from the runtime environment itself."
)
->addOption(
self::OPTION_IGNORE_CACHE,
null,
InputOption::VALUE_NONE,
"Ignores cached configuration data and always uses data fetched from S3."
)
->addOption(
self::OPTION_LOCAL_DIRECTORY_PATH,
null,
InputOption::VALUE_REQUIRED,
"Read application data files from a local directory. " .
"Intended for testing purposes only. " .
"Overrides the `--" . self::OPTION_IGNORE_CACHE . "` option."
);
}

/**
* {@inheritdoc}
* Reads common CLI options into class properties
*
* @param InputInterface $input
* @return void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function getCommonOptions(InputInterface $input): void
{
if ($input->getOption(self::OPTION_IGNORE_CACHE)) {
$this->useCache = false;
}
if ($input->getOption(self::OPTION_ENVIRONMENT)) {
$this->env = $input->getOption(self::OPTION_ENVIRONMENT);
}
if ($input->getOption(self::OPTION_LOCAL_DIRECTORY_PATH)) {
$this->localDirPath = $input->getOption(self::OPTION_LOCAL_DIRECTORY_PATH);
}
}

/**
Expand All @@ -92,15 +117,18 @@ protected function writeInfoHeader(OutputInterface $output, string $title, array
new OutputFormatterStyle('black', 'yellow', ['bold'])
);

$commonHeaderInfo = ['Environment' => $this->getEnv()];
if ($this->localDirPath === null) {
$commonHeaderInfo['Use cache?'] = ($this->getUseCache() ? "Yes" : "No");
} else {
$commonHeaderInfo['Local path'] = $this->localDirPath;
$commonHeaderInfo['Local path expanded'] = realpath($this->localDirPath);
}

$rows = array_merge($commonHeaderInfo, $headerInfo);

$maxLen = ['k' => 0, 'v' => 0];
$rows = array_merge(
[
"Environment" => $this->getEnv(),
"Use cache?" => ($this->getUseCache() ? "Yes" : "No")
],
$headerInfo
);
$output->writeln("");

foreach ($rows as $k => $v) {
if (strlen($k) > $maxLen['k']) {
$maxLen['k'] = strlen($k);
Expand All @@ -109,6 +137,8 @@ protected function writeInfoHeader(OutputInterface $output, string $title, array
$maxLen['v'] = strlen($v);
}
}

$output->writeln("");
$output->writeln(
"<header> " . str_pad('', $maxLen['k'] + $maxLen['v'] + 3, '-') . " </header>"
);
Expand All @@ -131,7 +161,13 @@ protected function writeInfoHeader(OutputInterface $output, string $title, array
protected function getCommonHelpText(): string
{
return "\nDefaults to using the current runtime environment. This can be overridden with\n" .
"the --" . self::OPTION_ENVIRONMENT . " argument.\n";
"the --" . self::OPTION_ENVIRONMENT . " argument.\n\n" .
"Defaults to using application data sourced from Amazon S3, and will use cached\n" .
"data (if it exists) unless the `" . self::OPTION_IGNORE_CACHE . "` option is set.\n\n" .
"This behaviour can be overriden by using the `" . self::OPTION_LOCAL_DIRECTORY_PATH .
"` option. This will\n" .
"look for application data in the provided local directory path (use this for testing\n" .
"purposes only - always use Amazon S3 sourced data for live web applications).\n";
}

protected function getEnv(): string
Expand All @@ -146,6 +182,6 @@ protected function getUseCache(): bool

protected function getDataLoader(): DataLoader
{
return $this->dataLoader;
return new DataLoader($this->getEnv(), $this->awsSdk, $this->psrCache, $this->localDirPath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
parent::execute($input, $output);
$this->getCommonOptions($input);

$appId = $input->getArgument(self::ARG_APP_ID);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
parent::execute($input, $output);
$this->getCommonOptions($input);
}
}
2 changes: 1 addition & 1 deletion src/ClientApplication/Cli/Command/ViewConfigCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
parent::execute($input, $output);
$this->getCommonOptions($input);

$headerInfo = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
parent::execute($input, $output);
$this->getCommonOptions($input);

$credsFile = $this->getDataLoader()->getCredentialsObjectName($this->getEnv());
$headerInfo = ['Credentials file' => $credsFile];
Expand Down
102 changes: 78 additions & 24 deletions src/ClientApplication/DataLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class DataLoader
private const ENVIRONMENTS = ['dev', 'test', 'production'];
private const S3_BUCKET_NAME = 'sws.clientapps';
private const S3_BASE_PATH = 'v2';
private const S3_COMMON_APP_DATA_NAME = 'apps.json';
private const S3_ENV_CREDENTIALS_NAME_PATTERN = 'credentials.__env__.json';
private const COMMON_APP_DATA_NAME = 'apps.json';
private const ENV_CREDENTIALS_NAME_PATTERN = 'credentials.__env__.json';
private const CREDENTIALS_ENV_PLACEHOLDER = '__env__';

/** @var string */
Expand All @@ -39,15 +39,24 @@ class DataLoader
/** @var CacheItemPoolInterface */
private $psrCache;

/** @var string */
private $localDirPath;

/**
* Constructs the object
*
* @param string $env Application environment
* @param AwsSdk $awsSdk AWS SDK
* @param CacheItemPoolInterface $psrCache PSR-6 cache item pool
* @param string $env Application environment
* @param AwsSdk $awsSdk AWS SDK
* @param CacheItemPoolInterface $psrCache PSR-6 cache item pool
* @param string $localDirPath Path to a directory where configuration files can be found.
* Overrides `$awsSdk` and `$psrCache` parameters.
*/
public function __construct(string $env, AwsSdk $awsSdk, CacheItemPoolInterface $psrCache)
{
public function __construct(
string $env,
AwsSdk $awsSdk,
CacheItemPoolInterface $psrCache,
string $localDirPath = null
) {
if (!in_array($env, self::ENVIRONMENTS)) {
throw new InvalidEnvironmentNameException(
'Invalid environment name `' . $env . '`. Must be one of `' .
Expand All @@ -59,6 +68,16 @@ public function __construct(string $env, AwsSdk $awsSdk, CacheItemPoolInterface
$this->awsSdk = $awsSdk;
$this->psrCache = $psrCache;

if ($localDirPath !== null) {
$this->localDirPath = realpath($localDirPath);
if ($this->localDirPath === false) {
throw new Exception("Invalid directory path '" . $this->localDirPath . "'. Path does not exist.");
}
if (!is_dir($this->localDirPath)) {
throw new Exception("Invalid directory path '" . $this->localDirPath . "'. Path is not a directory.");
}
}

// Load all environment data in `dev` environment
if ($this->env === 'dev') {
$this->loadEnv = self::ENVIRONMENTS;
Expand All @@ -83,7 +102,7 @@ public function getApp(string $env = null, bool $useCache = true): array
$credentialsObject = $this->getCredentialsObjectName($env);

return $this->mergeCredentials(
$this->getItem(self::S3_COMMON_APP_DATA_NAME, $useCache),
$this->getItem(self::COMMON_APP_DATA_NAME, $useCache),
$this->getItem($credentialsObject, $useCache),
$credentialsObject
);
Expand All @@ -95,6 +114,56 @@ public function getApp(string $env = null, bool $useCache = true): array
* @return array
*/
public function getItem(string $name, bool $useCache = true): array
{
if ($this->localDirPath !== null) {
return $this->loadFromLocalDirectory($name);
} else {
return $this->loadFromCache($name, $useCache);
}
}

/**
* Returns the name of an environment-specific credentials object
*
* @param string $env
* @return string
*/
public function getCredentialsObjectName(string $env): string
{
return str_replace(
self::CREDENTIALS_ENV_PLACEHOLDER,
$env,
self::ENV_CREDENTIALS_NAME_PATTERN
);
}

/**
* Load application data from a file in a local directory.
*
* @return array
*/
private function loadFromLocalDirectory(string $name): array
{
$filePath = rtrim($this->localDirPath, '/') . '/' . $name;
if (file_exists($filePath)) {
$data = json_decode((string)file_get_contents($filePath), true);
if ($data === null) {
throw new Exception("Invalid file path '$filePath'. File does not contain valid JSON.");
} else {
return $data;
}
} else {
throw new Exception("Invalid file path '$filePath'. File does not exist.");
}
}

/**
* Load application data from cache if available.
* If not, fetch from S3 and save to cache.
*
* @return array
*/
private function loadFromCache(string $name, bool $useCache = true): array
{
$s3ObjectName = self::S3_BASE_PATH . '/' . $name;

Expand All @@ -118,23 +187,8 @@ public function getItem(string $name, bool $useCache = true): array
$item->set($s3Data);
$item->expiresAt($expiryTime);
$this->psrCache->save($item);

return $s3Data;
}

/**
* Returns the name of an environment-specific credentials object
*
* @param string $env
* @return string
*/
public function getCredentialsObjectName(string $env): string
{
return str_replace(
self::CREDENTIALS_ENV_PLACEHOLDER,
$env,
self::S3_ENV_CREDENTIALS_NAME_PATTERN
);
return $s3Data;
}

/**
Expand Down
Loading

0 comments on commit 8e885ff

Please sign in to comment.