]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/protected/API/Class/BaculaSetting.php
baculum: Fix saving entire config by api request
[bacula/bacula] / gui / baculum / protected / API / Class / BaculaSetting.php
1 <?php
2 /*
3  * Bacula(R) - The Network Backup Solution
4  * Baculum   - Bacula web interface
5  *
6  * Copyright (C) 2013-2016 Kern Sibbald
7  *
8  * The main author of Baculum is Marcin Haba.
9  * The original author of Bacula is Kern Sibbald, with contributions
10  * from many others, a complete list can be found in the file AUTHORS.
11  *
12  * You may use this file and others of this release according to the
13  * license defined in the LICENSE file, which includes the Affero General
14  * Public License, v3.0 ("AGPLv3") and some additional permissions and
15  * terms pursuant to its AGPLv3 Section 7.
16  *
17  * This notice must be preserved when any source code is
18  * conveyed and/or propagated.
19  *
20  * Bacula(R) is a registered trademark of Kern Sibbald.
21  */
22
23 Prado::using('Application.Common.Class.Params');
24 Prado::using('Application.Common.Class.Errors');
25 Prado::using('Application.API.Class.BException');
26 Prado::using('Application.API.Class.APIModule');
27 Prado::using('Application.API.Class.APIConfig');
28
29 /**
30  * Read/write Bacula configuration.
31  *
32  * @author Marcin Haba <marcin.haba@bacula.pl>
33  */
34 class BaculaSetting extends APIModule {
35
36         const COMPONENT_DIR_TYPE = 'dir';
37         const COMPONENT_SD_TYPE = 'sd';
38         const COMPONENT_FD_TYPE = 'fd';
39         const COMPONENT_BCONS_TYPE = 'bcons';
40
41         private function getComponentTypes() {
42                 $types = array(
43                         self::COMPONENT_DIR_TYPE,
44                         self::COMPONENT_SD_TYPE,
45                         self::COMPONENT_FD_TYPE,
46                         self::COMPONENT_BCONS_TYPE
47                 );
48                 return $types;
49         }
50
51         public function getConfig($component_type = null, $resource_type = null, $resource_name = null) {
52                 $this->checkConfigSupport($component_type);
53                 $config = array();
54                 $json_tools = $this->Application->getModule('json_tools');
55                 if (!is_null($component_type)) {
56                         // get resources config
57                         $params = array();
58                         if ($component_type == self::COMPONENT_DIR_TYPE) {
59                                 $params['dont_apply_jobdefs'] = true;
60                         }
61                         if (!is_null($resource_type)) {
62                                 $params['resource_type'] = $resource_type;
63                         }
64                         if (!is_null($resource_name)) {
65                                 $params['resource_name'] = $resource_name;
66                         }
67                         $config = $json_tools->execCommand($component_type, $params);
68                 } else {
69                         // get components config
70                         $config = $this->getComponents();
71                 }
72                 return $config;
73         }
74
75         private function getComponents() {
76                 $components_info = array();
77                 $json_tools = $this->Application->getModule('json_tools');
78                 $components = $this->getSupportedComponents();
79                 $is_any = false;
80                 for ($i = 0; $i < count($components); $i++) {
81                         $component_type = $components[$i];
82                         $component_name = '';
83                         $error_msg = '';
84                         $resource_type = $this->Application->getModule('misc')->getMainComponentResource($component_type);
85                         $directive_name = 'Name';
86                         $params = array(
87                                 'resource_type' => $resource_type,
88                                 'directive_name' => $directive_name,
89                                 'data_only' => true
90                         );
91                         $result = $json_tools->execCommand($component_type, $params);
92                         $state = ($result['exitcode'] === 0 && is_array($result['output']));
93                         if ($state === true) {
94                                 $is_any = true;
95                                 if (count($result['output']) > 0) {
96                                         $component_directive = array_pop($result['output']);
97                                         if (array_key_exists($directive_name, $component_directive)) {
98                                                 $component_name = $component_directive[$directive_name];
99                                         }
100                                 }
101                         } else {
102                                 /**
103                                  * Unable to get component info (tool returned an error).
104                                  * Keep error message and continue with rest components.
105                                  */
106                                 $error_msg = $result['output'];
107                         }
108                         $component = array(
109                                 'component_type' => $component_type,
110                                 'component_name' => $component_name,
111                                 'state' => $state,
112                                 'error_msg' => $error_msg
113                         );
114                         array_push($components_info, $component);
115                 }
116
117                 $error = BaculaConfigError::ERROR_NO_ERRORS;
118                 if ($is_any === false) {
119                         $error = BaculaConfigError::ERROR_CONFIG_NO_JSONTOOL_READY;
120                 }
121                 $result = $json_tools->prepareResult($components_info, $error);
122                 return $result;
123         }
124
125         public function setConfig($config, $component_type, $resource_type = null, $resource_name = null) {
126                 $ret = array('is_valid' => false, 'save_result' => false, 'result' => null);
127                 $this->checkConfigSupport($component_type);
128                 $json_tools = $this->Application->getModule('json_tools');
129                 $params = array();
130                 if ($component_type == self::COMPONENT_DIR_TYPE) {
131                         $params['dont_apply_jobdefs'] = true;
132                 }
133                 $result = $json_tools->execCommand($component_type, $params);
134                 if ($result['exitcode'] === 0 && is_array($result['output'])) {
135                         $config_orig = $result['output'];
136                         if (!is_null($resource_type)) {
137                                 // Set single resource
138                                 $config_new = array($resource_type => $config);
139                         } else {
140                                 // Set whole config
141                                 $config_new = $config;
142                         }
143                         $ret = $this->saveConfig($config_orig, $config_new, $component_type, $resource_type, $resource_name);
144                 } else {
145                         $ret['result'] = $result;
146                 }
147                 return $ret;
148         }
149
150         private function saveConfig(array $config_orig, array $config_new, $component_type, $resource_type = null, $resource_name = null) {
151                 $config = array();
152
153                 if (!is_null($resource_type) && !is_null($resource_name)) {
154                         // Update single resource in config
155                         $config = $this->updateConfigResource($config_orig, $config_new, $resource_type, $resource_name);
156                 } elseif (count($config_orig) > 0) {
157                         // Update whole config
158                         $config = $this->updateConfig($config_orig, $config_new);
159                 } elseif (count($config_new) > 0) {
160                         // Add new config (create component config)
161                         $config = $config_new;
162                 }
163
164                 // Save config to file
165                 return $this->getModule('bacula_config')->setConfig($component_type, $config);
166         }
167
168         private function updateConfig(array $config_orig, array $config_new) {
169                 $config = array();
170                 for ($i = 0; $i < count($config_new); $i++) {
171                         $resource_new = $config_new[$i];
172                         $found = false;
173                         for ($j = 0; $j < count($config_orig); $j++) {
174                                 $resource_orig = $config_orig[$j];
175                                 if ($this->compareResources(array($resource_orig, $resource_new)) === true) {
176                                         // Resource type and name are the same. Update directives.
177                                         $config[] = $this->updateResource($resource_orig, $resource_new);
178                                         $found = true;
179                                         break;
180                                 }
181                         }
182                         if (!$found) {
183                                 // Newly added resource
184                                 $config[] = $resource_new;
185                         }
186                 }
187                 return $config;
188         }
189
190         private function updateConfigResource(array $config_orig, array $resource, $resource_type, $resource_name) {
191                 $config = array();
192                 $is_update = false;
193                 for ($i = 0; $i < count($config_orig); $i++) {
194                         $resource_orig = $config_orig[$i];
195                         if ($this->compareResources(array($resource_orig, $resource)) === true) {
196                                 // Resource type and name are the same. Update directives.
197                                 $config[] = $this->updateResource($resource_orig, $resource);
198                                 $is_update = true;
199                         } else {
200                                 // Rewrite not modified resource
201                                 $config[] = $this->updateResource($resource_orig, $resource_orig);
202                         }
203                 }
204                 if ($is_update === false) {
205                         // Existing resource with changed name, or new resource
206                         $resource_index = $this->getConfigResourceIndex($config, $resource_type, $resource_name);
207                         if (!is_null($resource_index)) {
208                                 // Existing resource
209                                 $resource_orig = $config[$resource_index];
210                                 // Remove existing resource
211                                 array_splice($config, $resource_index, 1);
212                                 // Add resource with new name
213                                 $config[] = $this->updateResource($resource_orig, $resource);
214                         } else {
215                                 // Add new resource
216                                 $config[] = $this->updateResource(array($resource_type => array()), $resource);
217                         }
218                 }
219                 return $config;
220         }
221
222         private function updateResource(array $resource_orig, array $resource_new) {
223                 $resource = array();
224                 $resource_type_orig = key($resource_orig);
225                 $resource_type_new = key($resource_new);
226
227                 if ($resource_type_new === 'Schedule') {
228                         $resource_type = $resource_type_new;
229                         $resource = array($resource_type => array());
230                         foreach ($resource_new[$resource_type] as $directive_name => $directive_value) {
231                                 if ($directive_name === 'Run') {
232                                         for($i = 0; $i < count($directive_value); $i++) {
233                                                 if (is_array($directive_value[$i])) {
234                                                         $overwrite_directive = array_map('overwrite_directives_callback', array_keys($directive_value[$i]), array_values($directive_value[$i]));
235                                                         $overwrite_directive = implode(' ', array_filter($overwrite_directive));
236                                                         $hour = $directive_value[$i]['Hour'][0];
237                                                         $hourly = '';
238                                                         if (count($directive_value[$i]['Hour']) === 24) {
239                                                                 $hourly = 'hourly';
240                                                         }
241                                                         $minute = '00';
242                                                         /**
243                                                          * Check if Minute key exists because of bug about missing Minute
244                                                          * @see http://bugs.bacula.org/view.php?id=2318
245                                                          */
246                                                         if (array_key_exists('Minute', $directive_value[$i])) {
247                                                                 $minute = sprintf('%02d', $directive_value[$i]['Minute']);
248                                                         }
249                                                         $day = count($directive_value[$i]['Day']) === 31 ? '' : 'on ' . implode(',', $directive_value[$i]['Day']);
250                                                         $month = Params::getMonthsConfig($directive_value[$i]['Month']);
251                                                         $week = Params::getWeeksConfig($directive_value[$i]['WeekOfMonth']);
252                                                         $wday = Params::getWdaysConfig($directive_value[$i]['DayOfWeek']);
253                                                         $value = array($overwrite_directive, $month, $week, $day, $wday, $hourly, 'at', "$hour:$minute");
254                                                         $value = array_filter($value);
255                                                         if (!array_key_exists($directive_name, $resource[$resource_type])) {
256                                                                 $resource[$resource_type][$directive_name] = array();
257                                                         }
258                                                         $resource[$resource_type][$directive_name][] = implode(' ', $value);
259                                                 } else {
260                                                         $resource[$resource_type][$directive_name][] = $directive_value[$i];
261                                                 }
262                                         }
263                                 } else {
264                                         $resource[$resource_type][$directive_name] = $this->formatDirectiveValue($directive_value);
265                                 }
266                         }
267
268                 } elseif ($resource_type_new === 'Messages') {
269                         $resource_type = $resource_type_new;
270                         $resource = array($resource_type => array());
271                         foreach ($resource_new[$resource_type] as $directive_name => $directive_value) {
272                                 if ($directive_name === 'Destinations') {
273                                         for ($i = 0; $i < count($directive_value); $i++) {
274                                                 $value = array();
275                                                 if (array_key_exists('Where', $directive_value[$i])) {
276                                                         array_push($value, implode(',', $directive_value[$i]['Where']));
277                                                 }
278                                                 array_push($value, implode(', ', $directive_value[$i]['MsgTypes']));
279                                                 $resource[$resource_type][$directive_value[$i]['Type']] = implode(' = ', $value);
280                                         }
281                                 } else {
282                                         $resource[$resource_type][$directive_name] = $directive_value;
283                                 }
284                         }
285
286                 } elseif ($resource_type_orig === $resource_type_new) {
287                         $resource_type = $resource_type_orig;
288                         $resource = array($resource_type => array());
289
290                         foreach ($resource_orig[$resource_type] as $directive_name => $directive_value) {
291                                 if (!array_key_exists($directive_name, $resource_new[$resource_type])) {
292                                         // directive removed in resource
293                                         continue;
294                                 }
295                                 if (is_array($resource_new[$resource_type][$directive_name])) {
296                                         // nested directive (name { key = val })
297                                         $resource[$resource_type][$directive_name] = $this->updateSubResource($resource_new[$resource_type][$directive_name]);
298                                 } else {
299                                         // simple directive (key=val)
300                                         // existing directive in resource
301                                         $resource[$resource_type][$directive_name] = $this->formatDirectiveValue($resource_new[$resource_type][$directive_name]);
302                                 }
303                         }
304                         foreach ($resource_new[$resource_type] as $directive_name => $directive_value) {
305                                 if (!array_key_exists($directive_name, $resource_orig[$resource_type])) {
306                                         // new directive in resource
307                                         $resource[$resource_type][$directive_name] = $this->formatDirectiveValue($directive_value);
308                                 }
309                         }
310                 } else {
311                         // It shouldn't happen.
312                         $this->getModule('logging')->log(
313                                 __FUNCTION__,
314                                 "Attemp to update resource with different resource types.",
315                                 Logging::CATEGORY_APPLICATION,
316                                 __FILE__,
317                                 __LINE__
318                         );
319                         $resource = $resource_orig;
320                 }
321                 return $resource;
322         }
323
324         private function updateSubResource(array $subresource_new) {
325                 $resource = array();
326                 foreach($subresource_new as $directive_name => $directive_value) {
327                         $check_recursive = false;
328                         if (is_array($directive_value)) {
329                                 $assoc_keys = array_filter(array_keys($directive_value), 'is_string');
330                                 $check_recursive = count($assoc_keys) > 0;
331                         }
332                         if ($check_recursive === true) {
333                                 $resource[$directive_name] = $this->updateSubResource($directive_value);
334                         } else {
335                                 $resource[$directive_name] = $this->formatDirectiveValue($directive_value);
336                         }
337                 }
338                 return $resource;
339         }
340
341
342         private function compareResources(array $resources) {
343                 $same_resource = false;
344                 $items = array('type' => array(), 'name' => array());
345                 $resources_count = count($resources);
346                 $counter = 0;
347                 for ($i = 0; $i < $resources_count; $i++) {
348                         if (count($resources[$i]) === 1) {
349                                 $resource_type = key($resources[$i]);
350                                 if (array_key_exists('Name', $resources[$i][$resource_type])) {
351                                         $items['type'][] = $resource_type;
352                                         $items['name'][] = $resources[$i][$resource_type]['Name'];
353                                         $counter++;
354                                 }
355                         }
356                 }
357                 if ($resources_count > 1 && $resources_count === $counter) {
358                         $result = false;
359                         foreach ($items as $key => $value) {
360                                 $result = (count(array_unique($value)) === 1);
361                                 if ($result === false) {
362                                         break;
363                                 }
364                         }
365                         $same_resource = $result;
366                 }
367                 return $same_resource;
368         }
369
370         private function getConfigResourceIndex($config, $resource_type, $resource_name) {
371                 $index = null;
372                 $find_resource = array($resource_type => array('Name' => $resource_name));
373                 for ($i = 0; $i < count($config); $i++) {
374                         if ($this->compareResources(array($config[$i], $find_resource)) === true) {
375                                 $index = $i;
376                                 break;
377                         }
378                 }
379                 return $index;
380         }
381
382         /**
383          * Format directive value.
384          * It is used on write config action to last prepare config before
385          * sending it to config writer.
386          *
387          * @param mixed $value directive value
388          * @return mixed formatted directive value
389          */
390         private function formatDirectiveValue($value) {
391                 $directive_value = null;
392                 if (is_bool($value)) {
393                         $directive_value = ($value === true) ? 'yes' : 'no';
394                 } elseif (is_int($value)) {
395                         $directive_value = $value;
396                 } elseif (is_string($value)) {
397                         $value = str_replace('"', '\"', $value);
398                         $value = "\"$value\"";
399                         $directive_value = $value;
400                 } elseif (is_array($value)) {
401                         // only simple numeric arrays
402                         $dvalues = array();
403                         for ($i = 0; $i < count($value); $i++) {
404                                 if (is_array($value[$i])) {
405                                         $dvalues[] = $this->updateSubResource($value[$i]);
406                                 } else {
407                                         $dvalues[] = $this->formatDirectiveValue($value[$i]);
408                                 }
409                         }
410                         $directive_value = $dvalues;
411                 } else {
412                         $emsg = sprintf("Attemp to format a directive value with not supported value type '%s'.", gettype($value));
413                         $this->getModule('logging')->log(
414                                 __FUNCTION__,
415                                 $emsg,
416                                 Logging::CATEGORY_APPLICATION,
417                                 __FILE__,
418                                 __LINE__
419                         );
420                 }
421                 return $directive_value;
422         }
423
424         /**
425          * Get supported component types.
426          * The support is determined by configured JSON tool in API config.
427          * If API is able to use JSON tool for specific component then the component is supported.
428          * Currently a component type is the same as related JSON tool type, but it can be
429          * changed in the future. From this reason components have theirown types.
430          *
431          * @return array supported component types
432          * @throws BConfigException if json tools support is disabled
433          */
434         public function getSupportedComponents() {
435                 $components = array();
436                 $types = $this->getComponentTypes();
437                 $tools = $this->getModule('api_config')->getSupportedJSONTools();
438                 for ($i = 0; $i < count($tools); $i++) {
439                         if (in_array($tools[$i], $types)) {
440                                 array_push($components, $tools[$i]);
441                         }
442                 }
443                 return $components;
444         }
445
446         /**
447          * Check if config support is configured and enabled
448          * globally and for specific component type.
449          *
450          * @private
451          * @param mixed $component_type component type for which config support is checked
452          * @throws BConfigException if support is not configured or disabled
453          */
454         private function checkConfigSupport($component_type = null) {
455                 $api_cfg = $this->getModule('api_config');
456                 if (!$api_cfg->isJSONToolsConfigured($component_type) || !$api_cfg->isJSONToolsEnabled()) {
457                         throw new BConfigException(
458                                 JSONToolsError::MSG_ERROR_JSON_TOOLS_DISABLED,
459                                 JSONToolsError::ERROR_JSON_TOOLS_DISABLED
460                         );
461                 } elseif (!is_null($component_type) && !$api_cfg->isJSONToolConfigured($component_type)) {
462                         $emsg = ' JSONTool=>' . $component_type;
463                         throw new BConfigException(
464                                 JSONToolsError::MSG_ERROR_JSON_TOOL_NOT_CONFIGURED . $emsg,
465                                 JSONToolsError::ERROR_JSON_TOOLS_DISABLED
466                         );
467                 }
468         }
469
470         /**
471          * Get JSON tool type by component type.
472          * Currently the mapping is one-to-one because each component type is the same
473          * as json tool type (dir == dir, bcons == bcons ...etc.). The method is for
474          * hypothetical case when component type is different than json tool type.
475          * It can be useful in future.
476          *
477          * @param string $component_type component type
478          * @return string json tool type
479          */
480         public function getJSONToolTypeByComponentType($component_type) {
481                 $tool_type = null;
482                 switch ($component_type) {
483                         case self::COMPONENT_DIR_TYPE: $tool_type = APIConfig::JSON_TOOL_DIR_TYPE; break;
484                         case self::COMPONENT_SD_TYPE: $tool_type = APIConfig::JSON_TOOL_SD_TYPE; break;
485                         case self::COMPONENT_FD_TYPE: $tool_type = APIConfig::JSON_TOOL_FD_TYPE; break;
486                         case self::COMPONENT_BCONS_TYPE: $tool_type = APIConfig::JSON_TOOL_BCONS_TYPE; break;
487                 }
488                 return $tool_type;
489         }
490
491         // REMOVE ???
492         public function testConfigDir($path) {
493                 $valid = is_writable($path);
494                 return $valid;
495         }
496 }
497 function overwrite_directives_callback($directive_name, $directive_value) {
498         $directive = '';
499         $overwrite_directives = array(
500                 'Level',
501                 'Pool',
502                 'Storage',
503                 'Messages',
504                 'FullPool',
505                 'DifferentialPool',
506                 'IncrementalPool',
507                 'Accurate',
508                 'Priority',
509                 'SpoolData',
510                 'WritePartAfterJob',
511                 'MaxRunSchedTime',
512                 'NextPool'
513         );
514         if (in_array($directive_name, $overwrite_directives)) {
515                 $directive = "{$directive_name}=\"{$directive_value}\"";
516         }
517         return $directive;
518 }
519 ?>