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

Feat/Google Cloud Storage sync #382

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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ If you are behind php 7.0 you can still use phpbu version [4.0.10](https://phar.
+ Dropbox
+ FTP
+ Google Drive
+ Google Cloud Storage
+ OpenStack
+ rsync
+ SFTP
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"php-opencloud/openstack": "^3.0",
"arhitector/yandex": "^2.0",
"microsoft/azure-storage-blob": "^1.4",
"phpmailer/phpmailer": "^6.0"
"phpmailer/phpmailer": "^6.0",
"google/cloud-storage": "^1.42"
},
"suggest": {
"sebastianfeldmann/ftp": "Require ^0.9.2 to sync to an FTP server",
Expand All @@ -79,7 +80,8 @@
"google/apiclient":"Require ^2.0 to sync to Google Drive",
"arhitector/yandex":"Require ^2.0 to sync to Yandex Disk",
"microsoft/azure-storage-blob": "Require ^1.4 to sync to Azure Blob Storage",
"phpmailer/phpmailer": "Require ^6.0 to receive logs via email"
"phpmailer/phpmailer": "Require ^6.0 to receive logs via email",
"google/cloud-storage": "Require ^1.42 to sync to Google Cloud Storage"
},
"bin": [
"phpbu"
Expand Down
8 changes: 8 additions & 0 deletions doc/config/sync/googlecloudstorage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "googlecloudstorage",
"options": {
"secret": "backup/google/client_secret.json",
"bucket": "my-bucket",
"path": "remote/path"
}
}
15 changes: 15 additions & 0 deletions doc/config/sync/googlecloudstorage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<sync type="googlecloudstorage">
<!-- google secret file -->
<option name="secret" value="backup/google/client_secret.json"/>

<!-- bucket name to upload to -->
<option name="bucket" value="backup/google/client_secret.json"/>

<!-- the remote path to upload to into the bucket -->
<option name="path" value="remote/path"/>

<!-- optional cleanup configuration -->
<option name="cleanup.type" value="quantity"/>
<option name="cleanup.amount" value="10"/>
</sync>
1 change: 1 addition & 0 deletions phpbu.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
"Dropbox",
"Ftp",
"GoogleDrive",
"GoogleCloudStorage",
"RSync",
"Sftp",
"Softlayer",
Expand Down
58 changes: 58 additions & 0 deletions src/Backup/Collector/GoogleCloudStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace phpbu\App\Backup\Collector;

use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\StorageObject;
use phpbu\App\Backup\Collector;
use phpbu\App\Backup\File;
use phpbu\App\Backup\Path;
use phpbu\App\Backup\Target;

/**
* GoogleCloud class.
*
* @package phpbu
* @subpackage Backup
* @author David Dattee <david.dattee@meetwashing.fr>
* @copyright Sebastian Feldmann <sebastian@phpbu.de>
* @license https://opensource.org/licenses/MIT The MIT License (MIT)
* @link http://phpbu.de/
*/
class GoogleCloudStorage extends Remote implements Collector
{
/**
* Bucket to read from.
*
* @var Bucket
*/
private Bucket $bucket;

/**
* @param Target $target
* @param Bucket $bucket
* @param Path $path
*/
public function __construct(Target $target, Bucket $bucket, Path $path)
{
$this->setUp($target, $path);
$this->bucket = $bucket;
}

/**
* Collect all created backups.
*
* @return void
*/
protected function collectBackups()
{
/** @var StorageObject $object */
foreach ($this->bucket->objects() as $object) {
if ($this->isFileMatch($this->path->getPath() . '/' . $object->name())) {
$file = new File\GoogleCloudStorage($object);

$this->files[$this->getFileIndex($file)] = $file;
}
}
}
}
54 changes: 54 additions & 0 deletions src/Backup/File/GoogleCloudStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace phpbu\App\Backup\File;

use Google\Cloud\Storage\StorageObject;
use phpbu\App\Exception;

/**
* Google Drive file class.
*
* @package phpbu
* @subpackage Backup
* @author David Dattee <david.dattee@meetwashing.fr>
* @copyright Sebastian Feldmann <sebastian@phpbu.de>
* @license https://opensource.org/licenses/MIT The MIT License (MIT)
* @link http://phpbu.de/
*/
class GoogleCloudStorage extends Remote
{
/**
* Google Storage Object
*
* @var StorageObject
*/
private StorageObject $object;

/**
* Constructor.
*
* @param StorageObject $googleStorageObject
*/
public function __construct(StorageObject $googleStorageObject)
{
$this->object = $googleStorageObject;
$this->filename = $this->object->name();
$this->pathname = $this->object->name();
$this->size = (int)$this->object->info()['size'];
$this->lastModified = strtotime($this->object->info()['updated']);
}

/**
* Deletes the file from Google Cloud Storage.
*
* @throws Exception
*/
public function unlink()
{
try {
$this->object->delete();
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
}
}
173 changes: 173 additions & 0 deletions src/Backup/Sync/GoogleCloudStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

namespace phpbu\App\Backup\Sync;

use Google\Cloud\Storage\StorageClient;
use phpbu\App\Backup\Collector;
use phpbu\App\Backup\Path;
use phpbu\App\Backup\Target;
use phpbu\App\Configuration;
use phpbu\App\Result;
use phpbu\App\Util;

/**
* Google Drive
*
* @package phpbu
* @subpackage Backup
* @author David Dattee <david.dattee@meetwashing.fr>
* @copyright Sebastian Feldmann <sebastian@phpbu.de>
* @license https://opensource.org/licenses/MIT The MIT License (MIT)
* @link http://phpbu.de/
*/
class GoogleCloudStorage implements Simulator
{
use Cleanable;

/**
* Google Gloud Storage client.
*
* @var StorageClient
*/
private $client;

/**
* Google json secret file.
*
* @var string
*/
private $secret;

/**
* Bucket to upload to
*
* @var string
*/
private $bucket;

/**
* Path to upload to in the bucket
*
* @var string
*/
private $parent;

/**
* (non-PHPDoc)
*
* @param array $options
* @throws \phpbu\App\Exception
* @see \phpbu\App\Backup\Sync::setup()
*/
public function setup(array $options)
{
if (!class_exists('\\Google\\Cloud\\Storage\\StorageClient')) {
throw new Exception('google cloud api client not loaded: use composer to install "google/cloud-storage"');
}
if (!Util\Arr::isSetAndNotEmptyString($options, 'secret')) {
throw new Exception('google secret json file is mandatory');
}
if (!Util\Arr::isSetAndNotEmptyString($options, 'bucket')) {
throw new Exception('bucket to upload to is mandatory');
}
$this->parent = Util\Arr::getValue($options, 'path', '');

$this->setupAuthFiles($options);
$this->setUpCleanable($options);
}

/**
* Make sure google authentication files exist and determine absolute path to them.
*
* @param array $config
* @throws \phpbu\App\Backup\Sync\Exception
*/
private function setupAuthFiles(array $config)
{
$secret = Util\Path::toAbsolutePath($config['secret'], Configuration::getWorkingDirectory());
if (!file_exists($secret)) {
throw new Exception(sprintf('google secret json file not found at %s', $secret));
}

$this->secret = $secret;
$this->bucket = $config['bucket'];
}

/**
* Execute the Sync
*
* @param \phpbu\App\Backup\Target $target
* @param \phpbu\App\Result $result
* @throws \phpbu\App\Backup\Sync\Exception
* @see \phpbu\App\Backup\Sync::sync()
*/
public function sync(Target $target, Result $result)
{
try {
$this->client = $this->createGoogleCloudClient();
$bucket = $this->client->bucket($this->bucket);
$hash = $this->calculateHash($target->getPathname());

$sentObject = $bucket->upload(
fopen($target->getPathname(), 'rb'),
[
'name' => ($this->parent ? $this->parent . '/' : '') . $target->getFilename(),
'metadata' => [
'crc32c' => $hash,
],
],
);

$result->debug(sprintf('upload: done: %s', $sentObject->name()));
$this->cleanup($target, $result);
} catch (\Exception $e) {
throw new Exception($e->getMessage(), 0, $e);
}
}

private function calculateHash(string $filePath): string
{
return base64_encode(hex2bin(hash_file('crc32c', $filePath)));
}

/**
* Simulate the sync execution.
*
* @param \phpbu\App\Backup\Target $target
* @param \phpbu\App\Result $result
*/
public function simulate(Target $target, Result $result)
{
$result->debug('sync backup to google cloud storage' . PHP_EOL);

$this->isSimulation = true;
$this->simulateRemoteCleanup($target, $result);
}

/**
* Setup google api client and google drive service.
*/
protected function createGoogleCloudClient(): StorageClient
{
if (!$this->client) {
$this->client = new StorageClient(['keyFilePath' => $this->secret]);
}

return $this->client;
}

/**
* Creates collector for remote cleanup.
*
* @param \phpbu\App\Backup\Target $target
* @return \phpbu\App\Backup\Collector
*/
protected function createCollector(Target $target): Collector
{
return new Collector\GoogleCloudStorage(
$target,
$this->createGoogleCloudClient()->bucket($this->bucket),
new Path($this->parent)
);
}
}
Loading