From: Marcin Haba Date: Mon, 30 Jan 2023 15:55:41 +0000 (+0100) Subject: baculum: Add support for cloud storage commands X-Git-Tag: Release-13.0.3~139 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e66bea2d91a3453dccaa64b24431084a121aa959;p=thirdparty%2Fbacula.git baculum: Add support for cloud storage commands Supported commands: - list - prune - truncate - upload --- diff --git a/gui/baculum/protected/API/Modules/Bconsole.php b/gui/baculum/protected/API/Modules/Bconsole.php index dc380e149..fa3f525b4 100644 --- a/gui/baculum/protected/API/Modules/Bconsole.php +++ b/gui/baculum/protected/API/Modules/Bconsole.php @@ -101,7 +101,8 @@ class Bconsole extends APIModule { '.query', '.jlist', '.search', - '@putfile' + '@putfile', + 'cloud' ); private $config; diff --git a/gui/baculum/protected/API/Modules/ConsoleOutputAPI2Page.php b/gui/baculum/protected/API/Modules/ConsoleOutputAPI2Page.php new file mode 100644 index 000000000..4e990d2c7 --- /dev/null +++ b/gui/baculum/protected/API/Modules/ConsoleOutputAPI2Page.php @@ -0,0 +1,54 @@ + + * @category API + * @package Baculum API + */ +abstract class ConsoleOutputAPI2Page extends ConsoleOutputPage { + + /** + * Parse API 2 interface type command output in "key=value" form. + * + * @param array $output command output + * @return array parsed output + */ + protected function parseOutput(array $output) { + $ret = $vals = []; + $out_len = count($output); + for ($i = 0; $i < $out_len; $i++) { + if (preg_match('/^\s*(?P\w+)\s*=\s*(?P.*?)$/i', $output[$i], $matches) === 1) { + $vals[$matches['key']] = $matches['value']; + } + if ((empty($output[$i]) || ($i == ($out_len - 1))) && count($vals) > 0) { + $ret[] = $vals; + $vals = []; + } + } + return $ret; + } +} diff --git a/gui/baculum/protected/API/Pages/API/StorageCloudList.php b/gui/baculum/protected/API/Pages/API/StorageCloudList.php new file mode 100644 index 000000000..7e7432a74 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageCloudList.php @@ -0,0 +1,116 @@ + + * @category API + * @package Baculum API + */ +class StorageCloudList extends ConsoleOutputAPI2Page { + + public function get() { + $misc = $this->getModule('misc'); + $storageid = $this->Request->contains('id') ? (int)$this->Request['id'] : 0; + $storage = $this->getModule('storage')->getStorageById($storageid); + $volume = $this->Request->contains('volume') && $misc->isValidName($this->Request['volume']) ? $this->Request['volume'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidInteger($this->Request['drive']) ? (int)$this->Request['drive'] : null; + $out_format = $this->Request->contains('output') && $this->isOutputFormatValid($this->Request['output']) ? $this->Request['output'] : parent::OUTPUT_FORMAT_RAW; + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + + $storage_exists = false; + if ($result->exitcode === 0) { + $storage_exists = (is_object($storage) && in_array($storage->name, $result->output)); + } + + if ($storage_exists == false) { + // Storage doesn't exist or is not available for user because of ACL restrictions + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + return; + } + + $out = (object)['output' => [], 'error' => 0]; + $params = [ + 'storage' => $storage->name + ]; + if (is_string($volume)) { + $params['volume'] = $volume; + } + if (is_int($drive)) { + $params['drive'] = $drive; + } + if ($out_format === ConsoleOutputPage::OUTPUT_FORMAT_RAW) { + $out = $this->getRawOutput($params); + } elseif ($out_format === ConsoleOutputPage::OUTPUT_FORMAT_JSON) { + $out = $this->getJSONOutput($params); + } + $this->output = $out['output']; + $this->error = $out['error']; + } + + protected function getRawOutput($params = [], $ptype = null) { + $cmd = ['cloud', 'list']; + foreach ($params as $key => $val) { + if (is_null($val)) { + $cmd[] = $key; + } else { + $cmd[] = $key . '="' . $val . '"'; + } + } + // traditional cloud storage truncate volume output + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + $cmd, + $ptype + ); + $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE; + $ret = [ + 'output' => $result->output, + 'error' => $error + ]; + return $ret; + } + + protected function getJSONOutput($params = []) { + $result = $this->getRawOutput( + $params, + Bconsole::PTYPE_API_CMD + ); + if ($result['error'] === 0) { + $result['output'] = $this->parseOutput($result['output']); + } + return $result; + } +} diff --git a/gui/baculum/protected/API/Pages/API/StorageCloudPrune.php b/gui/baculum/protected/API/Pages/API/StorageCloudPrune.php new file mode 100644 index 000000000..5de182a47 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageCloudPrune.php @@ -0,0 +1,118 @@ + + * @category API + * @package Baculum API + */ +class StorageCloudPrune extends BaculumAPIServer { + + public function get() { + $misc = $this->getModule('misc'); + $storageid = $this->Request->contains('id') ? (int)$this->Request['id'] : 0; + $storage = $this->getModule('storage')->getStorageById($storageid); + $volume = $this->Request->contains('volume') && $misc->isValidName($this->Request['volume']) ? $this->Request['volume'] : null; + $pool = $this->Request->contains('pool') && $misc->isValidName($this->Request['pool']) ? $this->Request['pool'] : null; + $allpools = $this->Request->contains('allpools') && $misc->isValidBooleanTrue($this->Request['allpools']) ? true : null; + $allfrompool = $this->Request->contains('allfrompool') && $misc->isValidBooleanTrue($this->Request['allfrompool']) ? true : null; + $mediatype = $this->Request->contains('mediatype') && $misc->isValidName($this->Request['mediatype']) ? $this->Request['mediatype'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidInteger($this->Request['drive']) ? (int)$this->Request['drive'] : null; + $slots = $this->Request->contains('slots') && $misc->isValidRange($this->Request['slots']) ? $this->Request['slots'] : null; + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + + $storage_exists = false; + if ($result->exitcode === 0) { + $storage_exists = (is_object($storage) && in_array($storage->name, $result->output)); + } + + if ($storage_exists == false) { + // Storage doesn't exist or is not available for user because of ACL restrictions + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + return; + } + + $out = (object)['output' => [], 'error' => 0]; + $params = [ + 'storage' => $storage->name + ]; + if (is_string($volume)) { + $params['volume'] = $volume; + } + if (is_bool($allpools)) { + $params['allpools'] = null; + } + if (is_bool($allfrompool)) { + $params['allfrompool'] = null; + } + if (is_string($pool)) { + $params['pool'] = $pool; + } + if (is_string($mediatype)) { + $params['mediatype'] = $mediatype; + } + if (is_int($drive)) { + $params['drive'] = $drive; + } + // Uncomment when slots parameter will be supported + /* + if (is_string($slots)) { + $params['slots'] = $slots; + } + */ + $out = $this->getRawOutput($params); + $this->output = $out['output']; + $this->error = $out['error']; + } + + protected function getRawOutput($params = []) { + $cmd = ['cloud', 'prune']; + foreach ($params as $key => $val) { + if (is_null($val)) { + $cmd[] = $key; + } else { + $cmd[] = $key . '="' . $val . '"'; + } + } + // traditional cloud storage truncate volume output + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + $cmd + ); + $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE; + $ret = [ + 'output' => $result->output, + 'error' => $error + ]; + return $ret; + } +} diff --git a/gui/baculum/protected/API/Pages/API/StorageCloudTruncate.php b/gui/baculum/protected/API/Pages/API/StorageCloudTruncate.php new file mode 100644 index 000000000..3945a9560 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageCloudTruncate.php @@ -0,0 +1,118 @@ + + * @category API + * @package Baculum API + */ +class StorageCloudTruncate extends BaculumAPIServer { + + public function get() { + $misc = $this->getModule('misc'); + $storageid = $this->Request->contains('id') ? (int)$this->Request['id'] : 0; + $storage = $this->getModule('storage')->getStorageById($storageid); + $volume = $this->Request->contains('volume') && $misc->isValidName($this->Request['volume']) ? $this->Request['volume'] : null; + $pool = $this->Request->contains('pool') && $misc->isValidName($this->Request['pool']) ? $this->Request['pool'] : null; + $allpools = $this->Request->contains('allpools') && $misc->isValidBooleanTrue($this->Request['allpools']) ? true : null; + $allfrompool = $this->Request->contains('allfrompool') && $misc->isValidBooleanTrue($this->Request['allfrompool']) ? true : null; + $mediatype = $this->Request->contains('mediatype') && $misc->isValidName($this->Request['mediatype']) ? $this->Request['mediatype'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidInteger($this->Request['drive']) ? (int)$this->Request['drive'] : null; + $slots = $this->Request->contains('slots') && $misc->isValidRange($this->Request['slots']) ? $this->Request['slots'] : null; + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + + $storage_exists = false; + if ($result->exitcode === 0) { + $storage_exists = (is_object($storage) && in_array($storage->name, $result->output)); + } + + if ($storage_exists == false) { + // Storage doesn't exist or is not available for user because of ACL restrictions + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + return; + } + + $out = (object)['output' => [], 'error' => 0]; + $params = [ + 'storage' => $storage->name + ]; + if (is_string($volume)) { + $params['volume'] = $volume; + } + if (is_bool($allpools)) { + $params['allpools'] = null; + } + if (is_bool($allfrompool)) { + $params['allfrompool'] = null; + } + if (is_string($pool)) { + $params['pool'] = $pool; + } + if (is_string($mediatype)) { + $params['mediatype'] = $mediatype; + } + if (is_int($drive)) { + $params['drive'] = $drive; + } + // Uncomment when slots parameter will be supported + /* + if (is_string($slots)) { + $params['slots'] = $slots; + } + */ + $out = $this->getRawOutput($params); + $this->output = $out['output']; + $this->error = $out['error']; + } + + protected function getRawOutput($params = []) { + $cmd = ['cloud', 'truncate']; + foreach ($params as $key => $val) { + if (is_null($val)) { + $cmd[] = $key; + } else { + $cmd[] = $key . '="' . $val . '"'; + } + } + // traditional cloud storage truncate volume output + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + $cmd + ); + $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE; + $ret = [ + 'output' => $result->output, + 'error' => $error + ]; + return $ret; + } +} diff --git a/gui/baculum/protected/API/Pages/API/StorageCloudUpload.php b/gui/baculum/protected/API/Pages/API/StorageCloudUpload.php new file mode 100644 index 000000000..294304355 --- /dev/null +++ b/gui/baculum/protected/API/Pages/API/StorageCloudUpload.php @@ -0,0 +1,118 @@ + + * @category API + * @package Baculum API + */ +class StorageCloudUpload extends BaculumAPIServer { + + public function get() { + $misc = $this->getModule('misc'); + $storageid = $this->Request->contains('id') ? (int)$this->Request['id'] : 0; + $storage = $this->getModule('storage')->getStorageById($storageid); + $volume = $this->Request->contains('volume') && $misc->isValidName($this->Request['volume']) ? $this->Request['volume'] : null; + $pool = $this->Request->contains('pool') && $misc->isValidName($this->Request['pool']) ? $this->Request['pool'] : null; + $allpools = $this->Request->contains('allpools') && $misc->isValidBooleanTrue($this->Request['allpools']) ? true : null; + $allfrompool = $this->Request->contains('allfrompool') && $misc->isValidBooleanTrue($this->Request['allfrompool']) ? true : null; + $mediatype = $this->Request->contains('mediatype') && $misc->isValidName($this->Request['mediatype']) ? $this->Request['mediatype'] : null; + $drive = $this->Request->contains('drive') && $misc->isValidInteger($this->Request['drive']) ? (int)$this->Request['drive'] : null; + $slots = $this->Request->contains('slots') && $misc->isValidRange($this->Request['slots']) ? $this->Request['slots'] : null; + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + array('.storage') + ); + + $storage_exists = false; + if ($result->exitcode === 0) { + $storage_exists = (is_object($storage) && in_array($storage->name, $result->output)); + } + + if ($storage_exists == false) { + // Storage doesn't exist or is not available for user because of ACL restrictions + $this->output = StorageError::MSG_ERROR_STORAGE_DOES_NOT_EXISTS; + $this->error = StorageError::ERROR_STORAGE_DOES_NOT_EXISTS; + return; + } + + $out = (object)['output' => [], 'error' => 0]; + $params = [ + 'storage' => $storage->name + ]; + if (is_string($volume)) { + $params['volume'] = $volume; + } + if (is_bool($allpools)) { + $params['allpools'] = null; + } + if (is_bool($allfrompool)) { + $params['allfrompool'] = null; + } + if (is_string($pool)) { + $params['pool'] = $pool; + } + if (is_string($mediatype)) { + $params['mediatype'] = $mediatype; + } + if (is_int($drive)) { + $params['drive'] = $drive; + } + // Uncomment when slots parameter will be supported + /* + if (is_string($slots)) { + $params['slots'] = $slots; + } + */ + $out = $this->getRawOutput($params); + $this->output = $out['output']; + $this->error = $out['error']; + } + + protected function getRawOutput($params = []) { + $cmd = ['cloud', 'upload']; + foreach ($params as $key => $val) { + if (is_null($val)) { + $cmd[] = $key; + } else { + $cmd[] = $key . '="' . $val . '"'; + } + } + // traditional cloud storage truncate volume output + $result = $this->getModule('bconsole')->bconsoleCommand( + $this->director, + $cmd + ); + $error = $result->exitcode == 0 ? $result->exitcode : GenericError::ERROR_WRONG_EXITCODE; + $ret = [ + 'output' => $result->output, + 'error' => $error + ]; + return $ret; + } +} diff --git a/gui/baculum/protected/API/Pages/API/endpoints.xml b/gui/baculum/protected/API/Pages/API/endpoints.xml index 45743da41..00aa6bfa9 100644 --- a/gui/baculum/protected/API/Pages/API/endpoints.xml +++ b/gui/baculum/protected/API/Pages/API/endpoints.xml @@ -35,6 +35,10 @@ + + + + diff --git a/gui/baculum/protected/API/openapi_baculum.json b/gui/baculum/protected/API/openapi_baculum.json index 55cfa6ad4..5fc72718e 100644 --- a/gui/baculum/protected/API/openapi_baculum.json +++ b/gui/baculum/protected/API/openapi_baculum.json @@ -2697,6 +2697,341 @@ ] } }, + "/api/v2/storages/{storageid}/cloud/upload": { + "get": { + "tags": ["storages"], + "summary": "Upload cloud volumes", + "description": "Upload cloud volumes", + "responses": { + "200": { + "description": "Upload cloud volume output", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + "type": "string", + "description": "Upload cloud volume output" + } + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 20, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/StorageId" + }, + { + "name": "volume", + "in": "query", + "required": false, + "description": "Volume name", + "schema": { + "type": "string" + } + }, + { + "name": "pool", + "in": "query", + "required": false, + "description": "Pool name", + "schema": { + "type": "string" + } + }, + { + "name": "allpools", + "in": "query", + "required": false, + "description": "Get volumes from all pools", + "schema": { + "type": "boolean" + } + }, + { + "name": "allfrompool", + "in": "query", + "required": false, + "description": "Get all from given pool in 'pool' parameter", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediatype", + "in": "query", + "required": false, + "description": "Media type", + "schema": { + "type": "string" + } + }, + { + "name": "drive", + "in": "query", + "required": false, + "description": "Drive number", + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/v2/storages/{storageid}/cloud/truncate": { + "get": { + "tags": ["storages"], + "summary": "Truncate cloud volumes", + "description": "Truncate cloud volumes", + "responses": { + "200": { + "description": "Truncate cloud volume output", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + "type": "string", + "description": "Truncate cloud volume output" + } + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 20, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/StorageId" + }, + { + "name": "volume", + "in": "query", + "required": false, + "description": "Volume name", + "schema": { + "type": "string" + } + }, + { + "name": "pool", + "in": "query", + "required": false, + "description": "Pool name", + "schema": { + "type": "string" + } + }, + { + "name": "allpools", + "in": "query", + "required": false, + "description": "Get volumes from all pools", + "schema": { + "type": "boolean" + } + }, + { + "name": "allfrompool", + "in": "query", + "required": false, + "description": "Get all from given pool in 'pool' parameter", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediatype", + "in": "query", + "required": false, + "description": "Media type", + "schema": { + "type": "string" + } + }, + { + "name": "drive", + "in": "query", + "required": false, + "description": "Drive number", + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/v2/storages/{storageid}/cloud/prune": { + "get": { + "tags": ["storages"], + "summary": "Prune cloud volumes", + "description": "Prune cloud volumes", + "responses": { + "200": { + "description": "Prune cloud volume output", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + "type": "string", + "description": "Prune cloud volume output" + } + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 20, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/StorageId" + }, + { + "name": "volume", + "in": "query", + "required": false, + "description": "Volume name", + "schema": { + "type": "string" + } + }, + { + "name": "pool", + "in": "query", + "required": false, + "description": "Pool name", + "schema": { + "type": "string" + } + }, + { + "name": "allpools", + "in": "query", + "required": false, + "description": "Get volumes from all pools", + "schema": { + "type": "boolean" + } + }, + { + "name": "allfrompool", + "in": "query", + "required": false, + "description": "Get all from given pool in 'pool' parameter", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediatype", + "in": "query", + "required": false, + "description": "Media type", + "schema": { + "type": "string" + } + }, + { + "name": "drive", + "in": "query", + "required": false, + "description": "Drive number", + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/v2/storages/{storageid}/cloud/list": { + "get": { + "tags": ["storages"], + "summary": "List cloud volumes", + "description": "List cloud volumes", + "responses": { + "200": { + "description": "List cloud volume output", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + "type": "string", + "description": "List cloud volume output" + } + }, + "error": { + "type": "integer", + "description": "Error code", + "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 20, 1000] + } + } + } + } + } + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/StorageId" + }, + { + "$ref": "#/components/parameters/Output" + }, + { + "name": "volume", + "in": "query", + "required": false, + "description": "Volume name", + "schema": { + "type": "string" + } + }, + { + "name": "drive", + "in": "query", + "required": false, + "description": "Drive number", + "schema": { + "type": "string" + } + } + ] + } + }, "/api/v2/storages/show": { "get": { "tags": ["storages"],