Skip to content

Commit

Permalink
add GCP tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrajodas committed Jan 21, 2025
1 parent 238b160 commit 71feb48
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 1 deletion.
16 changes: 16 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,29 @@ env:
KBC_DEVELOPERPORTAL_PASSWORD: ${{ secrets.KBC_DEVELOPERPORTAL_PASSWORD }}
DOCKERHUB_USER: keboolabot
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

# AWS Credentials
TEST_STORAGE_API_URL: https://connection.eu-central-1.keboola.com/
TEST_STORAGE_API_TOKEN: ${{ secrets.TEST_STORAGE_API_TOKEN }}
TEST_AWS_ACCESS_KEY_ID: AKIAQLZBTO5VMQJGVGNK
TEST_AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
TEST_AWS_REGION: eu-central-1
TEST_AWS_S3_BUCKET: ci-app-project-backup-s3filesbucket-7qyiq6iqfrse

# Azure Credentials
TEST_AZURE_STORAGE_API_URL: https://connection.north-europe.azure.keboola.com/
TEST_AZURE_STORAGE_API_TOKEN: ${{ secrets.TEST_AZURE_STORAGE_API_TOKEN }}
TEST_AZURE_ACCOUNT_NAME: ciappprojectbackup
TEST_AZURE_ACCOUNT_KEY: ${{ secrets.TEST_AZURE_ACCOUNT_KEY }}
TEST_AZURE_REGION: eu-west-1

# GCP Credentials
TEST_GCP_STORAGE_API_URL: "https://connection.europe-west3.gcp.keboola.com/"
TEST_GCP_STORAGE_API_TOKEN: ${{ secrets.TEST_GCP_STORAGE_API_TOKEN }}
TEST_GCP_SERVICE_ACCOUNT: ${{ secrets.TEST_GCP_SERVICE_ACCOUNT }}
TEST_GCP_BUCKET: "ci-app-project-backup"
TEST_GCP_REGION: "europe-west3"

jobs:
build:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -92,6 +103,11 @@ jobs:
-e TEST_AZURE_ACCOUNT_NAME
-e TEST_AZURE_ACCOUNT_KEY
-e TEST_AZURE_REGION
-e TEST_GCP_STORAGE_API_URL
-e TEST_GCP_STORAGE_API_TOKEN
-e TEST_GCP_BUCKET
-e TEST_GCP_REGION
-e TEST_GCP_SERVICE_ACCOUNT
$APP_IMAGE composer ci
deploy:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/vendor
/.idea
/data
.phpunit.result.cache
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ services:
- TEST_AZURE_ACCOUNT_NAME
- TEST_AZURE_ACCOUNT_KEY
- TEST_AZURE_REGION
- TEST_GCP_STORAGE_API_URL
- TEST_GCP_STORAGE_API_TOKEN
- TEST_GCP_BUCKET
- TEST_GCP_REGION
- TEST_GCP_SERVICE_ACCOUNT
12 changes: 12 additions & 0 deletions src/Config/ConfigDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ protected function getParametersDefinition(): ArrayNodeDefinition
->validate()->always(function ($v) {
if (!empty($v['storageBackendType'])) {
switch ($v['storageBackendType']) {
case Config::STORAGE_BACKEND_GCS:
foreach (['#jsonKey', '#bucket', 'region'] as $item) {
if (empty($v[$item])) {
throw new InvalidConfigurationException(sprintf(
'Missing required parameter "%s".',
$item,
));
}
}
break;
case Config::STORAGE_BACKEND_ABS:
foreach (['backupPath', 'accountName', '#accountKey'] as $item) {
if (empty($v[$item])) {
Expand Down Expand Up @@ -94,6 +104,8 @@ protected function getParametersDefinition(): ArrayNodeDefinition
->scalarNode('access_key_id')->end()
->scalarNode('#secret_access_key')->end()
->scalarNode('#bucket')->end()
->scalarNode('#jsonKey')->end()
->scalarNode('region')->end()
->end()
;
// @formatter:on
Expand Down
2 changes: 1 addition & 1 deletion src/Config/GcsConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class GcsConfig
public function __construct(array $params, readonly bool $isUserDefinedCredentials)
{
$this->jsonKey = $params['#jsonKey'];
$this->bucket = $params['bucket'];
$this->bucket = $params['#bucket'];
$this->region = $params['region'];
}

Expand Down
4 changes: 4 additions & 0 deletions src/Storages/GoogleCloudStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public function getBackup(Client $sapi, string $path): Backup
'keyFile' => json_decode($this->config->getJsonKey(), true),
]);

if (!str_ends_with($path, '/')) {
$path .= '/';
}

return new GcsBackup(
sapiClient: $sapi,
storageClient: $storageClient,
Expand Down
261 changes: 261 additions & 0 deletions tests/phpunit/FunctionalGCSTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
<?php

declare(strict_types=1);

namespace Keboola\App\ProjectBackup\Tests;

use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Aws\S3\S3UriParser;
use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Storage\StorageObject;
use Keboola\App\ProjectBackup\Config\Config;
use Keboola\Csv\CsvFile;
use Keboola\StorageApi\Client as StorageApi;
use Keboola\StorageApi\Components;
use Keboola\StorageApi\Options\Components\Configuration;
use Keboola\Temp\Temp;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;

class FunctionalGCSTest extends TestCase
{
protected Temp $temp;

protected StorageApi $sapiClient;

private string $testRunId;

public function setUp(): void
{
parent::setUp();

$this->temp = new Temp('project-backup');

$this->sapiClient = new StorageApi([
'url' => getenv('TEST_GCP_STORAGE_API_URL'),
'token' => getenv('TEST_GCP_STORAGE_API_TOKEN'),
]);

$this->cleanupKbcProject();

$this->storageClient = new StorageClient([
'keyFile' => json_decode((string) getenv('TEST_GCP_SERVICE_ACCOUNT'), true),
]);


$this->cleanupGCSBucket();

$component = new Components($this->sapiClient);

$config = new Configuration();
$config->setComponentId('keboola.snowflake-transformation');
$config->setDescription('Test Configuration');
$config->setConfigurationId('sapi-php-test');
$config->setName('test-configuration');
$component->addConfiguration($config);

$this->testRunId = $this->sapiClient->generateRunId();
}

public function testSuccessfulRun(): void
{
// run backup
$fileSystem = new Filesystem();
$fileSystem->dumpFile(
$this->temp->getTmpFolder() . '/config.json',
(string) json_encode([
'action' => 'run',
'parameters' => [
'backupId' => $this->sapiClient->generateId(),
],
'image_parameters' => [
'storageBackendType' => Config::STORAGE_BACKEND_GCS,
'#jsonKey' => getenv('TEST_GCP_SERVICE_ACCOUNT'),
'region' => getenv('TEST_GCP_REGION'),
'#bucket' => getenv('TEST_GCP_BUCKET'),
],
]),
);

$runProcess = $this->createTestProcess();
$runProcess->mustRun();

$this->assertEmpty($runProcess->getErrorOutput());

$output = $runProcess->getOutput();
$this->assertStringContainsString('Exporting buckets', $output);
$this->assertStringContainsString('Exporting tables', $output);
$this->assertStringContainsString('Exporting configurations', $output);
$this->assertStringContainsString('Exporting permanent files', $output);

$events = $this->sapiClient->listEvents(['runId' => $this->testRunId]);
self::assertGreaterThan(0, count($events));
}

public function testSuccessfulRunOnlyStructure(): void
{
$events = $this->sapiClient->listEvents(['runId' => $this->testRunId]);
self::assertCount(0, $events);

$tmp = new Temp();

$file = $tmp->createFile('testStructureOnly.csv');
file_put_contents($file->getPathname(), 'a,b,c,d,e,f');

$csvFile = new CsvFile($file->getPathname());

$this->sapiClient->createBucket('test-bucket', 'out');
$this->sapiClient->createTableAsync('out.c-test-bucket', 'test-table', $csvFile);

// run backup
$fileSystem = new Filesystem();
$fileSystem->dumpFile(
$this->temp->getTmpFolder() . '/config.json',
(string) json_encode([
'action' => 'run',
'parameters' => [
'backupId' => $this->sapiClient->generateId(),
'exportStructureOnly' => true,
],
'image_parameters' => [
'storageBackendType' => Config::STORAGE_BACKEND_GCS,
'#jsonKey' => getenv('TEST_GCP_SERVICE_ACCOUNT'),
'region' => getenv('TEST_GCP_REGION'),
'#bucket' => getenv('TEST_GCP_BUCKET'),
],
]),
);

$runProcess = $this->createTestProcess();
$runProcess->mustRun();

$this->assertEmpty($runProcess->getErrorOutput());

$output = $runProcess->getOutput();
$this->assertStringContainsString('Exporting buckets', $output);
$this->assertStringContainsString('Exporting tables', $output);
$this->assertStringContainsString('Exporting configurations', $output);
$this->assertStringNotContainsString('Table ', $output);

$events = $this->sapiClient->listEvents(['runId' => $this->testRunId]);
self::assertGreaterThan(0, count($events));
}

public function testCreateUnexistsBackupFolder(): void
{
$fileSystem = new Filesystem();
$fileSystem->dumpFile(
$this->temp->getTmpFolder() . '/config.json',
(string) json_encode([
'action' => 'run',
'parameters' => [
'storageBackendType' => Config::STORAGE_BACKEND_GCS,
'#jsonKey' => getenv('TEST_GCP_SERVICE_ACCOUNT'),
'region' => getenv('TEST_GCP_REGION'),
'#bucket' => getenv('TEST_GCP_BUCKET'),
'backupPath' => 'unexists/backup/folder',
],
'image_parameters' => [
'storageBackendType' => Config::STORAGE_BACKEND_GCS,
'access_key_id' => '',
'#secret_access_key' => '',
'region' => '',
'#bucket' => '',
],
]),
);

$runProcess = $this->createTestProcess();
$runProcess->run();

$this->assertEquals(0, $runProcess->getExitCode());

$objects = $this->storageClient->bucket((string) getenv('TEST_GCP_BUCKET'))->objects();

$files = [];
foreach ($objects as $object) {
$files[] = $object->name();
}

$this->assertNotEmpty($files);
}

public function testRegionErrorRun(): void
{
$fileSystem = new Filesystem();
$fileSystem->dumpFile(
$this->temp->getTmpFolder() . '/config.json',
(string) json_encode([
'action' => 'run',
'parameters' => [
'backupId' => $this->sapiClient->generateId(),
],
'image_parameters' => [
'storageBackendType' => Config::STORAGE_BACKEND_GCS,
'#jsonKey' => getenv('TEST_GCP_SERVICE_ACCOUNT'),
'region' => 'unknown-custom-region',
'#bucket' => getenv('TEST_GCP_BUCKET'),
],
]),
);

$runProcess = $this->createTestProcess();
$runProcess->run();

$this->assertEquals(2, $runProcess->getExitCode());

$output = $runProcess->getOutput();
$errorOutput = $runProcess->getErrorOutput();

$this->assertEmpty($output);
$this->assertStringContainsString('is not located in', $errorOutput);
$this->assertStringContainsString('unknown-custom-region', $errorOutput);
}

private function cleanupKbcProject(): void
{
$components = new Components($this->sapiClient);
foreach ($components->listComponents() as $component) {
foreach ($component['configurations'] as $configuration) {
$components->deleteConfiguration($component['id'], $configuration['id']);

// delete configuration from trash
$components->deleteConfiguration($component['id'], $configuration['id']);
}
}

// drop linked buckets
foreach ($this->sapiClient->listBuckets() as $bucket) {
if (isset($bucket['sourceBucket'])) {
$this->sapiClient->dropBucket($bucket['id'], ['force' => true]);
}
}

foreach ($this->sapiClient->listBuckets() as $bucket) {
$this->sapiClient->dropBucket($bucket['id'], ['force' => true]);
}
}

private function createTestProcess(): Process
{
$runCommand = 'php /code/src/run.php';
return Process::fromShellCommandline($runCommand, null, [
'KBC_DATADIR' => $this->temp->getTmpFolder(),
'KBC_URL' => getenv('TEST_GCP_STORAGE_API_URL'),
'KBC_TOKEN' => getenv('TEST_GCP_STORAGE_API_TOKEN'),
'KBC_RUNID' => $this->testRunId,
]);
}

private function cleanupGCSBucket(): void
{
$objects = $this->storageClient->bucket((string) getenv('TEST_GCP_BUCKET'))->objects();

/** @var StorageObject $object */
foreach ($objects as $object) {
$object->delete();
}
}
}

0 comments on commit 71feb48

Please sign in to comment.