deploy.module

Tracking 6.x-1.x branch
  1. drupal
    1. 6 contributions/deploy/deploy.module
    2. 7 contributions/deploy/deploy.module

Deployment API which enables modules to deploy items between servers.

This module defines the frameork for deployment, and a common API for managing plans and servers. Module-specific code (dependency checking, actual moving of things from one server to another) is in the module-specific modules.

Things deployment will not currently handle

  • Taxonomy terms assigned to multiple parents may or may not deploy properly. It is possible that only one parent will be properly attached.
  • Node revisions are not supported. Every time you deploy a change to a a node, and the target has revisions enabled, a new revision will be created. Any previous revisions on the source machine will not be deployed.
  • While deploy can handle updates and inserts, there is currently no provision for deploying deleted content.
  • Dependency management does not work properly for nodes of type 'page' at the moment. It is entirely possible for child nodes to be pushed before their parents.

@todo Address the fact that overall, the code weight behind deployment is enormous, by pulling everything except the user-facing add-to-plan stuff into admin.inc files.

Functions & methods

NameDescription
deploy_add_to_planAdd an item to a deployment plan.
deploy_ahah_auth_formA generic AHAH form callback that returns the authentication form for a server or a authentication type.
deploy_auth_invokeInvokes an authentication callback.
deploy_auth_key_argumentsImplementation of the arguments callback.
deploy_auth_key_formImplementation of the form callback for the authentication.
deploy_auth_sessid_argumentsImplementation of the arguments callback.
deploy_auth_sessid_cleanupImplementation of the cleanup callback.
deploy_auth_sessid_formImplementation of the form callback for the authentication.
deploy_auth_sessid_initImplementation of the init callback.
deploy_check_batchBatch API callback for the hook_deploy_check() process.
deploy_create_planCreate a new deployment plan.
deploy_delete_planDelete a deployment plan
deploy_deploy_auth_infoImplementation of hook_deploy_auth_info().
deploy_empty_planRemove all items from a deployment plan.
deploy_get_auth_key_hashReturns a hash to be used with the key authentication.
deploy_get_auth_typeGet a specific authentication type.
deploy_get_auth_typesGet all the available authentication types.
deploy_get_min_weightGet the current lowest weight in a specified plan.
deploy_get_planGet the details for a single deployment plan.
deploy_get_plansGet a list of all deployment plans.
deploy_get_plan_itemGet the details for a single deployment plan item.
deploy_get_plan_itemsRetrieve all the details for all items in a plan.
deploy_get_plan_optionsGet a list of all deployment plans, formatted appropriately for FAPI options.
deploy_get_remote_book
deploy_get_remote_key
deploy_get_serverGet the details for a single deployment plan server.
deploy_get_serversGet a list of all deployment plan servers.
deploy_get_server_formStandard form with server list. Used in many places.
deploy_helpImplementation of hook_help().
deploy_itemDeploy a specified item to a remote server.
deploy_item_batchWrapper function to deploy_item() with batch API goodness.
deploy_item_is_in_planDetermine whether a specified item is in a specified plan
deploy_menuImplementation of hook_menu().
deploy_permImplementation of hook_perm().
deploy_planDeploy the specified plan to a remote server
deploy_plan_checkRun the dependency checking hooks for the specified deployment plan.
deploy_plan_check_itemRun the dependency checking hook for one deployment item.
deploy_plan_check_item_batchWrapper function to deploy_plan_check_item() with batch API goodness.
deploy_plan_cleanupClean up after ourselves once a deployment is done.
deploy_plan_existsCheck to see if a plan already exists with a given name
deploy_plan_initInitiate depolyment.
deploy_plan_item_deleteDelete item from plan.
deploy_plan_push_formPush a deployment plan live form.
deploy_plan_push_form_submitSubmit callback for deploy_plan_push_form()
deploy_push_batchBatch API callback for the deployment push process.
deploy_sendPush an item from a deployment plan to the remote server.
deploy_themeImplementation of hook_theme().
deploy_update_itemUpdate a single item in a deployment plan with new data.
deploy_views_apiImplementation of hook_views_api().

Constants

NameDescription
DEPLOY_COMMENT_GROUP_WEIGHT
DEPLOY_CONTENT_TYPE_GROUP_WEIGHT
DEPLOY_FILE_GROUP_WEIGHT
DEPLOY_NODEQUEUE_GROUP_WEIGHT
DEPLOY_NODE_GROUP_WEIGHT
DEPLOY_SYSTEM_SETTINGS_GROUP_WEIGHTThese constants are used to help control the weighting of deployment plan items. The lower the number, the earlier it gets pushed (implying that items with higher weights may be dependent on items with lower ones.)
DEPLOY_TAXONOMY_TERM_GROUP_WEIGHT
DEPLOY_TAXONOMY_VOCABULARY_GROUP_WEIGHT
DEPLOY_USER_GROUP_WEIGHT
DEPLOY_VIEW_GROUP_WEIGHT

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Deployment API which enables modules to deploy items between servers.
  5. *
  6. * This module defines the frameork for deployment, and a common API for managing
  7. * plans and servers. Module-specific code (dependency checking, actual moving of
  8. * things from one server to another) is in the module-specific modules.
  9. *
  10. * Things deployment will not currently handle
  11. * - Taxonomy terms assigned to multiple parents may or may not deploy
  12. * properly. It is possible that only one parent will be properly attached.
  13. * - Node revisions are not supported. Every time you deploy a change to a
  14. * a node, and the target has revisions enabled, a new revision will be
  15. * created. Any previous revisions on the source machine will not be deployed.
  16. * - While deploy can handle updates and inserts, there is currently no provision
  17. * for deploying deleted content.
  18. * - Dependency management does not work properly for nodes of type 'page' at the
  19. * moment. It is entirely possible for child nodes to be pushed before their
  20. * parents.
  21. *
  22. * @todo Address the fact that overall, the code weight behind deployment is enormous,
  23. * by pulling everything except the user-facing add-to-plan stuff into admin.inc
  24. * files.
  25. */
  26. /**
  27. * These constants are used to help control the weighting of deployment
  28. * plan items. The lower the number, the earlier it gets pushed (implying
  29. * that items with higher weights may be dependent on items with lower ones.)
  30. *
  31. * @todo Move this into a table and implement a hook_deploy_weight so that modules
  32. * can define their own weights. Maybe? Need to do something, Eaton would not approve.
  33. */
  34. define('DEPLOY_SYSTEM_SETTINGS_GROUP_WEIGHT', 0);
  35. define('DEPLOY_CONTENT_TYPE_GROUP_WEIGHT', 1);
  36. define('DEPLOY_VIEW_GROUP_WEIGHT', 2);
  37. define('DEPLOY_TAXONOMY_VOCABULARY_GROUP_WEIGHT', 3);
  38. define('DEPLOY_TAXONOMY_TERM_GROUP_WEIGHT', 4);
  39. define('DEPLOY_USER_GROUP_WEIGHT', 5);
  40. define('DEPLOY_FILE_GROUP_WEIGHT', 6);
  41. define('DEPLOY_NODE_GROUP_WEIGHT', 7);
  42. define('DEPLOY_COMMENT_GROUP_WEIGHT', 8);
  43. define('DEPLOY_NODEQUEUE_GROUP_WEIGHT', 9);
  44. /**
  45. * Implementation of hook_menu().
  46. */
  47. function deploy_menu() {
  48. $items = array();
  49. // Deployment batch processes
  50. $items['admin/build/deploy/deploy_check_batch'] = array(
  51. 'title' => 'Deployment checking batch process',
  52. 'page callback' => 'deploy_check_batch',
  53. 'access arguments' => array('deploy items'),
  54. 'type' => MENU_CALLBACK,
  55. 'description' => 'Deploy content and settings between Drupal servers.',
  56. );
  57. $items['admin/build/deploy/deploy_push_batch'] = array(
  58. 'title' => 'Deployment pushing batch process',
  59. 'page callback' => 'deploy_push_batch',
  60. 'access arguments' => array('deploy items'),
  61. 'type' => MENU_CALLBACK,
  62. 'description' => 'Deploy content and settings between Drupal servers.',
  63. );
  64. // Deployment plan management.
  65. $items['admin/build/deploy'] = array(
  66. 'title' => 'Deployment',
  67. 'page callback' => 'deploy_overview',
  68. 'access arguments' => array('administer deployment'),
  69. 'description' => 'Deploy content and settings between Drupal servers.',
  70. 'file' => 'deploy.plans.admin.inc',
  71. );
  72. $items['admin/build/deploy/plans'] = array(
  73. 'title' => 'Plans',
  74. 'type' => MENU_DEFAULT_LOCAL_TASK,
  75. 'access arguments' => array('administer deployment'),
  76. );
  77. $items['admin/build/deploy/add'] = array(
  78. 'title' => 'Add a deployment plan',
  79. 'description' => 'Add a deployment plan.',
  80. 'page callback' => 'drupal_get_form',
  81. 'page arguments' => array('deploy_plan_form'),
  82. 'access arguments' => array('administer deployment'),
  83. 'file' => 'deploy.plans.admin.inc',
  84. 'type' => MENU_CALLBACK,
  85. 'weight' => 2
  86. );
  87. $items['admin/build/deploy/plan'] = array(
  88. 'title' => 'Edit plan',
  89. 'page callback' => 'drupal_get_form',
  90. 'page arguments' => array('deploy_plan_form'),
  91. 'access arguments' => array('administer deployment'),
  92. 'file' => 'deploy.plans.admin.inc',
  93. 'type' => MENU_CALLBACK,
  94. );
  95. $items['admin/build/deploy/list'] = array(
  96. 'title' => 'View deployment plan items',
  97. 'page callback' => 'drupal_get_form',
  98. 'page arguments' => array('deploy_list_form'),
  99. 'type' => MENU_CALLBACK,
  100. 'file' => 'deploy.plans.admin.inc',
  101. 'access arguments' => array('administer deployment'),
  102. 'weight' => 1,
  103. );
  104. $items['admin/build/deploy/delete/item'] = array(
  105. 'title' => 'Delete a deployment plan item',
  106. 'page callback' => 'drupal_get_form',
  107. 'page arguments' => array('deploy_delete_item_form'),
  108. 'type' => MENU_CALLBACK,
  109. 'file' => 'deploy.plans.admin.inc',
  110. 'access arguments' => array('administer deployment'),
  111. 'weight' => 1,
  112. );
  113. $items['admin/build/deploy/delete/plan'] = array(
  114. 'title' => 'Delete a deployment plan',
  115. 'page callback' => 'drupal_get_form',
  116. 'page arguments' => array('deploy_delete_plan_form'),
  117. 'type' => MENU_CALLBACK,
  118. 'file' => 'deploy.plans.admin.inc',
  119. 'access arguments' => array('administer deployment'),
  120. 'weight' => 1,
  121. );
  122. // Deployment server management.
  123. $items['admin/build/deploy/servers'] = array(
  124. 'title' => 'Servers',
  125. 'description' => 'Manage deployment servers',
  126. 'page callback' => 'deploy_server_overview',
  127. 'access arguments' => array('administer deployment'),
  128. 'type' => MENU_LOCAL_TASK,
  129. 'file' => 'deploy.servers.admin.inc',
  130. 'weight' => 3
  131. );
  132. $items['admin/build/deploy/server/add'] = array(
  133. 'title' => 'Add server',
  134. 'description' => 'Add a deployment server.',
  135. 'page callback' => 'drupal_get_form',
  136. 'page arguments' => array('deploy_server_form'),
  137. 'access arguments' => array('administer deployment'),
  138. 'file' => 'deploy.servers.admin.inc',
  139. 'type' => MENU_CALLBACK,
  140. 'weight' => 2
  141. );
  142. $items['admin/build/deploy/server'] = array(
  143. 'title' => 'Edit server',
  144. 'description' => 'Edit a deployment server.',
  145. 'page callback' => 'drupal_get_form',
  146. 'page arguments' => array('deploy_server_form'),
  147. 'file' => 'deploy.servers.admin.inc',
  148. 'access arguments' => array('administer deployment'),
  149. 'type' => MENU_CALLBACK,
  150. );
  151. $items['admin/build/deploy/delete/server'] = array(
  152. 'title' => 'Delete a server',
  153. 'page callback' => 'drupal_get_form',
  154. 'page arguments' => array('deploy_delete_server_form', 5),
  155. 'type' => MENU_CALLBACK,
  156. 'file' => 'deploy.servers.admin.inc',
  157. 'access arguments' => array('administer deployment'),
  158. 'weight' => 1,
  159. );
  160. // Server form AHAH callback.
  161. $items['admin/build/deploy/ahah/auth-form'] = array(
  162. 'page callback' => 'deploy_ahah_auth_form',
  163. 'access arguments' => array('administer deployment'),
  164. 'type' => MENU_CALLBACK,
  165. );
  166. // Deployment logs
  167. $items['admin/build/deploy/logs'] = array(
  168. 'title' => 'Deployment Log',
  169. 'description' => 'View logs of past deployments',
  170. 'page callback' => 'deploy_logs_overview',
  171. 'access arguments' => array('administer deployment'),
  172. 'file' => 'deploy.logs.admin.inc',
  173. 'type' => MENU_LOCAL_TASK,
  174. 'weight' => 4
  175. );
  176. $items['admin/build/deploy/logs/details'] = array(
  177. 'title' => 'Deployment Log Details',
  178. 'description' => 'View detailed logs of a past deployment',
  179. 'page callback' => 'deploy_logs_details',
  180. 'file' => 'deploy.logs.admin.inc',
  181. 'access arguments' => array('administer deployment'),
  182. 'type' => MENU_CALLBACK,
  183. );
  184. // Deployment settings
  185. $items['admin/build/deploy/settings'] = array(
  186. 'title' => 'Settings',
  187. 'description' => 'Manage deployment settings',
  188. 'page callback' => 'drupal_get_form',
  189. 'page arguments' => array('deploy_settings'),
  190. 'access arguments' => array('administer deployment'),
  191. 'file' => 'deploy.settings.admin.inc',
  192. 'type' => MENU_LOCAL_TASK,
  193. 'weight' => 4
  194. );
  195. $items['admin/build/deploy/push'] = array(
  196. 'title' => 'Push a plan live',
  197. 'description' => 'Push a plan live',
  198. 'page callback' => 'drupal_get_form',
  199. 'page arguments' => array('deploy_plan_push_form'),
  200. 'access arguments' => array('deploy items'),
  201. 'type' => MENU_CALLBACK,
  202. );
  203. $items['admin/build/deploy/push/results'] = array(
  204. 'title' => 'Push results',
  205. 'page callback' => 'deploy_push_results',
  206. 'type' => MENU_CALLBACK,
  207. 'access arguments' => array('deploy items'),
  208. 'weight' => 1,
  209. );
  210. return $items;
  211. }
  212. /**
  213. * Implementation of hook_help().
  214. */
  215. function deploy_help($path, $args) {
  216. $output='';
  217. switch ($path) {
  218. case 'admin/help#deploy':
  219. return t('Allows users to deploy objects between servers.');
  220. case 'admin/build/deploy/push':
  221. return t('The Deploy module requires a user account on the remote server to authenticate against. This user will require the appropriate permissions for items being deployed (administer nodes, create users, etc.) It is recommended that a unique user and role be created specifically for deployment purposes.');
  222. }
  223. }
  224. /**
  225. * Implementation of hook_theme().
  226. */
  227. function deploy_theme() {
  228. return array(
  229. 'deploy_list_form' => array(
  230. 'arguments' => array('form' => NULL),
  231. ),
  232. );
  233. }
  234. /**
  235. * Implementation of hook_perm().
  236. */
  237. function deploy_perm() {
  238. return array(
  239. 'administer deployment',
  240. 'add items to deployment plan',
  241. 'deploy items'
  242. );
  243. }
  244. /**
  245. * Get a list of all deployment plans.
  246. *
  247. * @param $show_internal
  248. * Indicates whether or not internal-only plans should be listed.
  249. * @return $plans
  250. * Associative array of all deployment plans.
  251. */
  252. function deploy_get_plans($show_internal = FALSE) {
  253. $plans = array();
  254. $where = '';
  255. if (!$show_internal) {
  256. $where = 'where internal = 0';
  257. }
  258. $result = db_query("SELECT * FROM {deploy_plan} $where ORDER BY name");
  259. while ($plan = db_fetch_array($result)) {
  260. $plans[$plan['pid']] = $plan;
  261. }
  262. return $plans;
  263. }
  264. /**
  265. * Get a list of all deployment plans, formatted appropriately for FAPI options.
  266. *
  267. * @param $show_internal
  268. * Indicates whether or not internal-only plans should be listed.
  269. * @return $plans
  270. * Associative array of all deployment plans ('pid' => 'name').
  271. */
  272. function deploy_get_plan_options($show_internal = FALSE) {
  273. $options = array();
  274. $plans = deploy_get_plans($show_internal);
  275. foreach ($plans as $plan) {
  276. $options[$plan['pid']] = $plan['name'];
  277. }
  278. return $options;
  279. }
  280. /**
  281. * Add an item to a deployment plan.
  282. *
  283. * @param $pid
  284. * Unique identifier for the plan this item is being added to.
  285. * @param $module
  286. * The module that handles this "thing" being deployed.
  287. * @param $description
  288. * A text description of this item, to be displayed in the plan overview listing.
  289. * @param $data
  290. * The identifying data for this item (typically an ID, but it doesn't have to be.)
  291. * @param $weight
  292. * This item's weight within its group.
  293. * @param $weight_group
  294. * Group-centric weighting to control when the invidual modules are deployed.
  295. * For more details see the comments for the constants defined at the top of this module.
  296. * @todo This needs to be refactored such that a) it detects errors properly and b) it returns
  297. * a bool rather than just going on.
  298. */
  299. function deploy_add_to_plan($pid, $module, $description, $data, $weight = 0, $weight_group = 0) {
  300. global $user;
  301. if (!empty($data) && !empty($module) && !empty($pid)) {
  302. db_query("INSERT INTO {deploy_plan_items} (pid, uid, ts, module, description, data, weight, weight_group) VALUES (%d, %d, %d, '%s', '%s', '%s', %d, %d)", $pid, $user->uid, time(), $module, $description, $data, $weight, $weight_group);
  303. }
  304. }
  305. /**
  306. * Push a deployment plan live form.
  307. *
  308. * Prompts the user to choose what server they wish to deploy to before
  309. * proceeding with the good stuff.
  310. *
  311. * @param $form_state
  312. * FAPI form state
  313. * @param $pid
  314. * Unique identifier of the plan we're pushing.
  315. * @param $sid
  316. * Which server to use for the deployment.
  317. * @return
  318. * FAPI form definition
  319. * @ingroup forms
  320. * @see deploy_plan_push_form_submit()
  321. */
  322. function deploy_plan_push_form($form_state, $pid, $sid = NULL) {
  323. $form = deploy_get_server_form();
  324. $form['pid'] = array(
  325. '#type' => 'hidden',
  326. '#value' => $pid,
  327. );
  328. return $form;
  329. }
  330. /**
  331. * Submit callback for deploy_plan_push_form()
  332. *
  333. * This is where the actual action takes place.
  334. *
  335. * @todo Check system config on remote host - installed modules, proper versions, etc.
  336. * @todo Better error message handling that is not dependent on xmlrpc_error.
  337. */
  338. function deploy_plan_push_form_submit($form, &$form_state) {
  339. // Setup some data
  340. global $user;
  341. $pid = $form_state['values']['pid'];
  342. $sid = $form_state['values']['sid'];
  343. // If a session is successfully created, then go on to the deploy_check
  344. // batch process. Otherwise quit out and show the error log.
  345. if (deploy_plan_init($pid, $sid, $form_state['values'])) {
  346. $form_state['redirect'] = "admin/build/deploy/deploy_check_batch";
  347. }
  348. else {
  349. $dlid = variable_get('deploy_log_id', '');
  350. deploy_plan_cleanup();
  351. $form_state['redirect'] = "admin/build/deploy/logs/details/$dlid";
  352. }
  353. }
  354. /**
  355. * Batch API callback for the hook_deploy_check() process.
  356. */
  357. function deploy_check_batch() {
  358. $operations = array();
  359. $pid = variable_get('deploy_pid', '');
  360. $items = deploy_get_plan_items($pid);
  361. watchdog('plan items', print_r($items, TRUE));
  362. // The batch operations are calls to deploy_plan_check_item_batch(), which is
  363. // just a wrapper for deploy_plan_check_item() plus the batch api messaging.
  364. foreach ($items as $item) {
  365. $operations[] = array('deploy_plan_check_item_batch', array($item['module'], $item['data'], $item['description']));
  366. }
  367. // Also call the deploy_check_cleanup() hook at the end.
  368. $operations[] = array('module_invoke_all', array('deploy_check_cleanup', $pid));
  369. // And fire our batch. This batch may add items to the deployment plan,
  370. // thus increasing our operations for the actual push. So once the
  371. // checking is done, redirect to a second batch process for the actual
  372. // pushing.
  373. $batch = array(
  374. 'operations' => $operations,
  375. 'title' => t('Processing deployment plan dependencies.'),
  376. 'init_message' => t('Deployment dependency checking is starting.'),
  377. 'progress_message' => t('Checking item @current out of @total.'),
  378. 'error_message' => t('Deployment dependency checking has encountered an error.'),
  379. );
  380. batch_set($batch);
  381. batch_process("admin/build/deploy/deploy_push_batch");
  382. }
  383. /**
  384. * Wrapper function to deploy_plan_check_item() with batch API goodness.
  385. *
  386. * @param $module
  387. * The module which handles this item.
  388. * @param $data
  389. * The identifying data for this item
  390. * @param $description
  391. * The item's description for feedback messages
  392. * @param $context
  393. * Batch API context var for messages and results
  394. */
  395. function deploy_plan_check_item_batch($module, $data, $description, &$context) {
  396. deploy_plan_check_item($module, $data);
  397. // Update our progress information.
  398. $context['message'] = t('Now processing %item', array('%item' => $description));
  399. }
  400. /**
  401. * Batch API callback for the deployment push process.
  402. */
  403. function deploy_push_batch() {
  404. $operations = array();
  405. $pid = variable_get('deploy_pid', '');
  406. $items = deploy_get_plan_items($pid);
  407. // The batch oeprations are calls to deploy_item_batch(), which is just a wrapper
  408. // to deploy_item() plus batch pi messaging.
  409. foreach ($items as $item) {
  410. $operations[] = array('deploy_item_batch', array($item));
  411. }
  412. // And call deploy_plan_cleanup() when done.
  413. $operations[] = array('deploy_plan_cleanup', array());
  414. $batch = array(
  415. 'operations' => $operations,
  416. 'title' => t('Pushing deployment plan.'),
  417. 'init_message' => t('Deployment is starting.'),
  418. 'progress_message' => t('Pushing item @current out of @total.'),
  419. 'error_message' => t('Deployment has encountered an error.'),
  420. );
  421. // When complete, redirect to the log for this deployment.
  422. $dlid = variable_get('deploy_log_id', '');
  423. batch_set($batch);
  424. batch_process("admin/build/deploy/logs/details/$dlid");
  425. }
  426. /**
  427. * Wrapper function to deploy_item() with batch API goodness.
  428. *
  429. * @param $item
  430. * The item being deployed.
  431. * @param $context
  432. * Batch API context var for messages and results
  433. */
  434. function deploy_item_batch($item, &$context) {
  435. deploy_item($item);
  436. // Update our progress information. Error handling is all managed in deploy_item()
  437. // and logging is all done in the depoyment log, so we don't even worry about that.
  438. $context['message'] = t('Now processing %item', array('%item' => $item['description']));
  439. }
  440. /**
  441. * Get a list of all deployment plan servers.
  442. *
  443. * @return $servers
  444. * Associative array of all deployment plan servers ('sid' => 'description').
  445. */
  446. function deploy_get_servers() {
  447. $result = db_query("SELECT * FROM {deploy_servers} ORDER BY description");
  448. $servers = array();
  449. while ($server = db_fetch_array($result)) {
  450. $servers[$server['sid']] = $server['description'];
  451. }
  452. return $servers;
  453. }
  454. /**
  455. * Update a single item in a deployment plan with new data.
  456. *
  457. * Currently I believe this is only used by system_settings_deploy
  458. * since there is no easy way to uniquely identify and retrieve
  459. * them. See that module for more details.
  460. *
  461. * @param $iid
  462. * Unique identifier for this deployment plan item
  463. * @param $data
  464. * The new data to be saved for this item.
  465. */
  466. function deploy_update_item($iid, $data) {
  467. db_query("UPDATE {deploy_plan_items} SET data = '%s' WHERE iid = %d", $data, $iid);
  468. }
  469. /**
  470. * Get the details for a single deployment plan.
  471. *
  472. * @param $pid
  473. * Unique identifier for the plan whose details are being retrieved.
  474. */
  475. function deploy_get_plan($pid) {
  476. $result = db_query("SELECT * FROM {deploy_plan} WHERE pid = %d", $pid);
  477. return db_fetch_array($result);
  478. }
  479. /**
  480. * Get the details for a single deployment plan server.
  481. *
  482. * @param $sid
  483. * Unique identifier for the server whose details are being retrieved.
  484. */
  485. function deploy_get_server($sid) {
  486. $result = db_query("SELECT * FROM {deploy_servers} WHERE sid = %d", $sid);
  487. return db_fetch_array($result);
  488. }
  489. /**
  490. * Get the details for a single deployment plan item.
  491. *
  492. * @param $iid
  493. * Unique identifier for the plan item whose details are being retrieved.
  494. */
  495. function deploy_get_plan_item($iid) {
  496. $result = db_query("SELECT * FROM {deploy_plan_items} WHERE iid = %d", $iid);
  497. return db_fetch_array($result);
  498. }
  499. /**
  500. * Retrieve all the details for all items in a plan.
  501. *
  502. * Takes an optional module name, to return just the items for
  503. * that module.
  504. *
  505. * @param $pid
  506. * Unique identifier for the plan whose items you are retrieving.
  507. * @param $module
  508. * Restrict the items to just those associated with this module.
  509. */
  510. function deploy_get_plan_items($pid, $module = NULL) {
  511. $items = array();
  512. $sql = "SELECT * FROM {deploy_plan_items} WHERE pid = %d";
  513. if (!is_null($module)) {
  514. $sql .= " and module = '%s'";
  515. }
  516. $sql .= " order by weight_group, weight";
  517. $result = db_query($sql, $pid, $module);
  518. while ($item = db_fetch_array($result)) {
  519. $items[] = $item;
  520. }
  521. return $items;
  522. }
  523. /**
  524. * Determine whether a specified item is in a specified plan
  525. *
  526. * @param $pid
  527. * Unique identifier for the plan we're checking.
  528. * @param $module
  529. * The module that is associated with this item.
  530. * @param $data
  531. * Identifying data for this item (usually the item's original ID.)
  532. */
  533. function deploy_item_is_in_plan($pid, $module, $data) {
  534. return db_result(db_query("SELECT iid FROM {deploy_plan_items} WHERE pid = %d AND module = '%s' AND data = '%s'", $pid, $module, $data));
  535. }
  536. /**
  537. * Get the current lowest weight in a specified plan.
  538. *
  539. * Used so that items can force themselves to be weighted "lighter" than
  540. * anything else currently in the plan.
  541. *
  542. * @param $pid
  543. * Unique identifier for the plan we want to check.
  544. */
  545. function deploy_get_min_weight($pid) {
  546. return db_result(db_query("SELECT MIN(weight) FROM {deploy_plan_items} WHERE pid = %d", $pid));
  547. }
  548. /**
  549. * Push an item from a deployment plan to the remote server.
  550. *
  551. * @param protocol_args
  552. * An array of arguments related to the protocol you're using. For the moment this
  553. * is just the name of the function being called, but in the future it could include
  554. * more or vary depending on the transport mechanism.
  555. * @param function_args
  556. * An array of the necessary arguments for the function.
  557. * @return
  558. * Results of the remote call.
  559. */
  560. function deploy_send($protocol_args, $function_args) {
  561. // Get the active server.
  562. $server = variable_get('deploy_server', array());
  563. array_unshift($protocol_args, $server['url']);
  564. deploy_auth_invoke($server['auth_type'], 'arguments callback', $protocol_args, $function_args);
  565. return call_user_func_array('xmlrpc', array_merge($protocol_args, $function_args));
  566. }
  567. /**
  568. * Create a new deployment plan.
  569. *
  570. * @param $name
  571. * A unique name for the plan
  572. * @param $description
  573. * An optional description
  574. * @return int
  575. * The new plan's unique ID, or 0 on failure.
  576. */
  577. function deploy_create_plan($name, $description = '', $internal = 0) {
  578. $pid = 0;
  579. if (!deploy_plan_exists($name)) {
  580. db_query("INSERT INTO {deploy_plan} (name, description, internal) VALUES ('%s', '%s', %d)", $name, $description, $internal);
  581. $pid = db_last_insert_id('deploy_plan', 'pid');
  582. }
  583. return $pid;
  584. }
  585. /**
  586. * Check to see if a plan already exists with a given name
  587. *
  588. * @param $name
  589. * Plan name to check.
  590. * @return $pid
  591. * Plan's pid, or 0 if not found.
  592. */
  593. function deploy_plan_exists($name) {
  594. return db_result(db_query("SELECT pid FROM {deploy_plan} WHERE name = '%s'", $name));
  595. }
  596. /**
  597. * Remove all items from a deployment plan.
  598. *
  599. * @param $pid
  600. * Unique ID of the plan whose items should be removed.
  601. */
  602. function deploy_empty_plan($pid) {
  603. db_query("DELETE FROM {deploy_plan_items} WHERE pid = %d", $pid);
  604. }
  605. /**
  606. * Delete a deployment plan
  607. *
  608. * @param $pid
  609. * Unique ID of the plan to delete.
  610. */
  611. function deploy_delete_plan($pid) {
  612. deploy_empty_plan($pid);
  613. db_query("DELETE FROM {deploy_plan} WHERE pid = %d", $pid);
  614. }
  615. /**
  616. * Initiate depolyment.
  617. *
  618. * @param $pid
  619. * ID of the plan to deploy.
  620. * @param $sid
  621. * ID of the server to deploy to.
  622. * @param $settings
  623. * Server settings that was posted from the server form.
  624. */
  625. function deploy_plan_init($pid, $sid, $settings = array()) {
  626. include_once('./includes/xmlrpc.inc');
  627. global $user;
  628. $plan = deploy_get_plan($pid);
  629. $server = deploy_get_server($sid);
  630. // Abort if we didn't find a plan or server.
  631. if (empty($plan) || empty($server)) {
  632. return FALSE;
  633. }
  634. // Also add settings that came from the submitted server form.
  635. $server['settings'] = $settings;
  636. // Save this data out so the other modules can get it. Not sure of a better
  637. // way to handle this.
  638. variable_set('deploy_server', $server);
  639. variable_set('deploy_pid', $pid);
  640. // This used to be a static within deploy_item(), but batch API breaks
  641. // statics so I was forced down this route instead.
  642. variable_set('deploy_fatal', FALSE);
  643. // Rather than save foreign keys out to the server/plan/user in the log,
  644. // I'm saving actual identifying data. This keeps the log pure in case
  645. // associated data gets deleted.
  646. db_query("INSERT INTO {deploy_log} (plan, server, username, ts) VALUES ('%s', '%s', '%s', %d)", $plan['name'], $server['description'], $user->name, time());
  647. variable_set('deploy_log_id', db_last_insert_id('deploy_log', 'dlid'));
  648. // Allow other modules to do stuff before the content gets pushed.
  649. module_invoke_all('deploy_pre_plan', $pid);
  650. return deploy_auth_invoke($server['auth_type'], 'init callback', $server);
  651. }
  652. /**
  653. * Run the dependency checking hooks for the specified deployment plan.
  654. *
  655. * @param $pid
  656. * Unique ID of the plan to check.
  657. */
  658. function deploy_plan_check($pid) {
  659. // Call the dependency-checking hook for each item in the plan.
  660. // Someday I may want to aggregate each item of a type (node, user, etc)
  661. // into one array and call a hook once for each module to reduce hook
  662. // calling overhead. Worthwhile?
  663. $items = deploy_get_plan_items($pid);
  664. foreach ($items as $item) {
  665. deploy_plan_check_item($item['module'], $item['data']);
  666. }
  667. // If anyone needs to do any final cleanup now that dependencies are
  668. // sorted out, feel free.
  669. module_invoke_all('deploy_check_cleanup', $pid);
  670. }
  671. /**
  672. * Run the dependency checking hook for one deployment item.
  673. *
  674. * @param $module
  675. * The module handling this item.
  676. * @param $data
  677. * The identifying data or this item.
  678. */
  679. function deploy_plan_check_item($module, $data) {
  680. module_invoke($module, 'deploy_check', $data);
  681. }
  682. /**
  683. * Deploy the specified plan to a remote server
  684. *
  685. * @param $pid
  686. * Unique ID of the plan to deploy.
  687. */
  688. function deploy_plan($pid) {
  689. $items = deploy_get_plan_items($pid);
  690. foreach ($items as $item) {
  691. deploy_item($item);
  692. }
  693. }
  694. /**
  695. * Deploy a specified item to a remote server.
  696. *
  697. * @param $item
  698. * The item being deployed.
  699. */
  700. function deploy_item($item) {
  701. // By default, xmlrpc.inc is only included when xmlrpc() is called.
  702. // Since I am using xmlrpcerror() as an error handler (unfortunately)
  703. // there is an edge case where I'll need this loaded before I've called
  704. // xmlrpc(). So I include it manually here.
  705. include_once('./includes/xmlrpc.inc');
  706. // Static error flag so that we can use it between calls and not have to
  707. // handle errors in the batch process.
  708. $deploy_fatal = variable_get('deploy_fatal', FALSE);
  709. // If nothing has previously errored out, then try and deploy the current item.
  710. // Otherwise, note in the log that this item was not deployed due to previous error.
  711. if (!$deploy_fatal) {
  712. // Call the module's deployment function.
  713. $xmlrpc_result = module_invoke($item['module'], 'deploy', $item['data']);
  714. // If it results in failure, log the error message and set the $deploy_fatal
  715. // flag. Otherise log success and move onto the next one.
  716. if ($xmlrpc_result === FALSE) {
  717. variable_set('deploy_fatal', TRUE);
  718. $result = t('Error');
  719. $message = xmlrpc_error_msg();
  720. }
  721. else {
  722. $result = t('Success');
  723. $message = '';
  724. }
  725. }
  726. else {
  727. $result = t('Not Sent');
  728. $message = t('Item not sent due to prior fatal error.');
  729. }
  730. // And log the results.
  731. db_query("INSERT INTO {deploy_log_details} (dlid, module, description, result, message) VALUES (%d, '%s', '%s', '%s', '%s')", variable_get('deploy_log_id', ''), $item['module'], $item['description'], $result, $message);
  732. }
  733. /**
  734. * Clean up after ourselves once a deployment is done.
  735. */
  736. function deploy_plan_cleanup() {
  737. // clear remote cache
  738. deploy_send(array('system.cacheClearAll'), array());
  739. // Grab the pid so we can pass it to the post deploy hook.
  740. $pid = variable_get('deploy_pid', '');
  741. $server = variable_get('deploy_server', '');
  742. deploy_auth_invoke($server['auth_type'], 'cleanup callback');
  743. variable_del('deploy_server');
  744. variable_del('deploy_pid');
  745. variable_del('deploy_log_id');
  746. variable_del('deploy_fatal');
  747. // Allow other modules to do post-deployment tasks.
  748. module_invoke_all('deploy_post_plan', $pid);
  749. }
  750. function deploy_get_remote_key($uuid, $module) {
  751. // As remote keys are retrieved, they are cached in this static var. On
  752. // the next request, if that key exists, just return it rather than going
  753. // for another round trip. The format is
  754. //
  755. // $key_cache[$uuid] = $remote_key
  756. static $key_cache = array();
  757. if (isset($key_cache[$uuid])) {
  758. return $key_cache[$uuid];
  759. }
  760. // the remote data always comes back as array('uuid' => $uuid, '<key>' = $key)
  761. $remote_data = deploy_send(array('deploy_uuid.get_key'), array($uuid, $module));
  762. if ($remote_data) {
  763. $key_cache[$uuid] = $remote_data;
  764. }
  765. return $remote_data;
  766. }
  767. function deploy_get_remote_book($uuid) {
  768. // As remote keys are retrieved, they are cached in this static var. On
  769. // the next request, if that key exists, just return it rather than going
  770. // for another round trip. The format is
  771. //
  772. // $key_cache[$uuid] = $remote_key
  773. static $key_cache = array();
  774. if (isset($key_cache[$uuid])) {
  775. return $key_cache[$uuid];
  776. }
  777. $remote_data = deploy_send(array('deploy_uuid.get_book'), array($uuid));
  778. if ($remote_data) {
  779. $key_cache[$uuid] = $remote_data;
  780. }
  781. return $remote_data;
  782. }
  783. /**
  784. * Standard form with server list. Used in many places.
  785. */
  786. function deploy_get_server_form() {
  787. $servers = deploy_get_servers();
  788. if (empty($servers)) {
  789. drupal_set_message(t("There are no servers defined. Please define a server using the Servers tab before pushing your deployment plan."));
  790. drupal_goto("admin/build/deploy");
  791. }
  792. // Rebuild the server list so we also have an empty option.
  793. $options = array('' => t('-- Select a server'));
  794. foreach ($servers as $sid => $server) {
  795. $options[$sid] = $server;
  796. }
  797. $form['sid'] = array(
  798. '#title' => t('Server'),
  799. '#type' => 'select',
  800. '#options' => $options,
  801. '#description' => t('Select the server you want to deploy to'),
  802. '#required' => TRUE,
  803. '#ahah' => array(
  804. 'path' => 'admin/build/deploy/ahah/auth-form',
  805. 'wrapper' => 'deploy-auth-wrapper',
  806. 'method' => 'replace',
  807. ),
  808. );
  809. $form['auth_wrapper'] = array(
  810. '#prefix' => '<div id="deploy-auth-wrapper">',
  811. '#suffix' => '</div>',
  812. );
  813. // This form element will be replaced with the response from an AHAH request.
  814. $form['auth_wrapper']['settings'] = array(
  815. '#type' => 'hidden',
  816. );
  817. $form['submit'] = array(
  818. '#type' => 'submit',
  819. '#value' => t('Push Deployment Plan'),
  820. );
  821. return $form;
  822. }
  823. /**
  824. * A generic AHAH form callback that returns the authentication form for a
  825. * server or a authentication type.
  826. */
  827. function deploy_ahah_auth_form() {
  828. $cached_form_state = array();
  829. $cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state);
  830. $server = isset($_POST['sid']) ? deploy_get_server($_POST['sid']) : array();
  831. if (isset($_POST['auth_type'])) {
  832. $auth_type = $_POST['auth_type'];
  833. }
  834. elseif (isset($server['auth_type'])) {
  835. $auth_type = $server['auth_type'];
  836. }
  837. // If we've got a server and an auth type, add the appropriate form.
  838. if (!empty($server) && !empty($auth_type)) {
  839. $settings = deploy_auth_invoke($auth_type, 'form callback', $server);
  840. $cached_form['auth_wrapper']['settings'] = $settings;
  841. }
  842. // We proably selected <none>, so unset the settings form so we get an
  843. // empty auth form.
  844. else {
  845. unset($cached_form['auth_wrapper']['settings']);
  846. }
  847. form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);
  848. $form_state = array('submitted' => FALSE);
  849. $options = form_builder('deploy_ahah_auth_form', $cached_form['auth_wrapper'], $form_state);
  850. $output = drupal_render($options);
  851. print drupal_to_js(
  852. array(
  853. 'status' => TRUE,
  854. 'data' => $output,
  855. )
  856. );
  857. exit;
  858. }
  859. /**
  860. * Implementation of hook_views_api().
  861. */
  862. function deploy_views_api() {
  863. return array(
  864. 'api' => 2,
  865. 'path' => drupal_get_path('module', 'deploy') . '/includes',
  866. );
  867. }
  868. /**
  869. * Invokes an authentication callback.
  870. */
  871. function deploy_auth_invoke($type_name, $callback_type, &$a1 = NULL, &$a2 = NULL, &$a3 = NULL) {
  872. $type = deploy_get_auth_type($type_name);
  873. if (!isset($type[$callback_type]) || $type[$callback_type] === TRUE) {
  874. return TRUE;
  875. }
  876. else {
  877. $function = $type[$callback_type];
  878. return $function($a1, $a2, $a3);
  879. }
  880. }
  881. /**
  882. * Get all the available authentication types.
  883. *
  884. * @return
  885. * An associative array of the authentication type definitions.
  886. */
  887. function deploy_get_auth_types() {
  888. static $types = array();
  889. if (!empty($types)) {
  890. return $types;
  891. }
  892. $auth_modules = module_implements('deploy_auth_info');
  893. foreach ($auth_modules as $module) {
  894. $items = module_invoke($module, 'deploy_auth_info');
  895. // Add the machine readable name to the item it self.
  896. foreach ($items as $name => $item) {
  897. $item['name'] = $name;
  898. $types[$name] = $item;
  899. }
  900. }
  901. // Let other modules alter all authentication types.
  902. drupal_alter('deploy_auth_type', $types);
  903. return $types;
  904. }
  905. /**
  906. * Get a specific authentication type.
  907. *
  908. * @param $name
  909. * The name of the type to get.
  910. * @return
  911. * The authentication type definition.
  912. */
  913. function deploy_get_auth_type($name) {
  914. $types = deploy_get_auth_types();
  915. return $types[$name];
  916. }
  917. /**
  918. * Returns a hash to be used with the key authentication.
  919. *
  920. * @param $key
  921. * The API key to be used in the hash.
  922. * @param $timestamp
  923. * The timestamp to be used in the hash.
  924. * @param $domain
  925. * The domain to be used in the hash.
  926. * @param $nonce
  927. * The nonce to be used in the hash.
  928. * @param $method
  929. * The method to be used in the hash.
  930. * @todo
  931. * Add signed arguments.
  932. */
  933. function deploy_get_auth_key_hash($key, $timestamp, $domain, $nonce, $method) {
  934. $hash_parameters = array($timestamp, $domain, $nonce, $method);
  935. return hash_hmac('sha256', implode(';', $hash_parameters), $key);
  936. }
  937. /**
  938. * Implementation of hook_deploy_auth_info().
  939. *
  940. * This hook implements support for the two basic authentication types
  941. * that comes with the Services module by default. Those are authentication
  942. * with just a valid session id, or authentication with just an API key.
  943. *
  944. * Session authentication (with username and password) does only make
  945. * sense when deploying against another Drupal sites (which isn't always the
  946. * case).
  947. *
  948. * @return
  949. * An array of this modules' authentication types.
  950. * @todo
  951. * Make it so callbacks can be stored in a separate file.
  952. */
  953. function deploy_deploy_auth_info() {
  954. $items['deploy_key'] = array(
  955. 'title' => t('Key authentication'),
  956. 'description' => t('All method calls must include a valid token to authenticate themselves with the server.'),
  957. 'form callback' => 'deploy_auth_key_form',
  958. 'arguments callback' => 'deploy_auth_key_arguments',
  959. );
  960. $items['deploy_sessid'] = array(
  961. 'title' => t('Session id'),
  962. 'description' => t('All method calls must include a valid session id.'),
  963. 'form callback' => 'deploy_auth_sessid_form',
  964. 'init callback' => 'deploy_auth_sessid_init',
  965. 'arguments callback' => 'deploy_auth_sessid_arguments',
  966. 'cleanup callback' => 'deploy_auth_sessid_cleanup',
  967. );
  968. return $items;
  969. }
  970. /**
  971. * Implementation of the init callback.
  972. */
  973. function deploy_auth_sessid_init($server = array()) {
  974. // In order to prevent bots from cluttering up the sessions table, you must
  975. // have an active anonymous session before logging in to Drupal. So that is
  976. // the first thing we do with system.connect. This session ID is saved
  977. // to the 'deploy_sessid' variable, which all other xmlrpc calls to the
  978. // remote server passes.
  979. $result = deploy_send(array('system.connect'), array());
  980. variable_set('deploy_auth_sessid', $result['sessid']);
  981. // Then we can log in.
  982. $result = deploy_send(array('user.login'), array($server['settings']['username'], $server['settings']['password']));
  983. // If it fails, then add a message to the log. Otherwise set the session ID into
  984. // a drupal variable for the other functions to grab.
  985. if ($result === FALSE) {
  986. db_query("INSERT INTO {deploy_log_details} (dlid, module, description, result, message) VALUES (%d, '%s', '%s', '%s', '%s')", variable_get('deploy_log_id', ''), 'login', 'Remote user login', 'Error', xmlrpc_error_msg());
  987. }
  988. else {
  989. variable_set('deploy_auth_sessid', $result['sessid']);
  990. return TRUE;
  991. }
  992. }
  993. /**
  994. * Implementation of the arguments callback.
  995. */
  996. function deploy_auth_key_arguments(&$protocol_args, &$function_args) {
  997. // Get the active server.
  998. $server = variable_get('deploy_server', array());
  999. $timestamp = time();
  1000. $method = $protocol_args[1];
  1001. // Use Drupal's built in password generator to generate a random string.
  1002. $nonce = user_password();
  1003. $hash = deploy_get_auth_key_hash($server['settings']['key'], $timestamp, $server['settings']['domain'], $nonce, $method);
  1004. array_push($protocol_args, $hash, $server['settings']['domain'], $timestamp, $nonce);
  1005. }
  1006. /**
  1007. * Implementation of the arguments callback.
  1008. */
  1009. function deploy_auth_sessid_arguments(&$protocol_args, &$function_args) {
  1010. // Check the method name. The server URL is the first argument.
  1011. if ($protocol_args[1] != 'system.connect') {
  1012. $sessid = variable_get('deploy_auth_sessid', '');
  1013. array_push($protocol_args, $sessid);
  1014. }
  1015. }
  1016. /**
  1017. * Implementation of the cleanup callback.
  1018. */
  1019. function deploy_auth_sessid_cleanup() {
  1020. variable_del('deploy_auth_sessid');
  1021. }
  1022. /**
  1023. * Implementation of the form callback for the authentication.
  1024. */
  1025. function deploy_auth_key_form($server = array()) {
  1026. $form['key'] = array(
  1027. '#type' => 'textfield',
  1028. '#title' => t('API key'),
  1029. '#description' => t('The API key to use when authenticating with the remote server.'),
  1030. '#required' => TRUE,
  1031. );
  1032. $form['domain'] = array(
  1033. '#type' => 'textfield',
  1034. '#title' => t('Domain'),
  1035. '#description' => t('Domain using this key (note this is not necessarily the same as your domain name).'),
  1036. '#required' => TRUE,
  1037. );
  1038. return $form;
  1039. }
  1040. /**
  1041. * Implementation of the form callback for the authentication.
  1042. */
  1043. function deploy_auth_sessid_form($server = array()) {
  1044. $form['username'] = array(
  1045. '#type' => 'textfield',
  1046. '#title' => t('Username'),
  1047. '#size' => 30,
  1048. '#maxlength' => 60,
  1049. '#description' => t('Username on the remote site.'),
  1050. '#required' => TRUE,
  1051. );
  1052. $form['password'] = array(
  1053. '#type' => 'password',
  1054. '#title' => t('Password'),
  1055. '#size' => 20,
  1056. '#maxlength' => 32,
  1057. '#description' => t('Password of the remote user Deploy should log in as.'),
  1058. '#required' => TRUE,
  1059. );
  1060. return $form;
  1061. }
  1062. /**
  1063. * Delete item from plan.
  1064. *
  1065. * @param $conditions
  1066. * An array of fields and values used to build a sql WHERE clause indicating
  1067. * what items should be deleted.
  1068. */
  1069. function deploy_plan_item_delete($conditions = array()) {
  1070. $schema = drupal_get_schema('deploy_plan_items');
  1071. $where = array();
  1072. // Build an array of fields with the appropriate placeholders for use in
  1073. // db_query().
  1074. foreach ($conditions as $field => $value) {
  1075. $where[] = $field . ' = ' . db_type_placeholder($schema['fields'][$field]['type']);
  1076. }
  1077. // Now implode that array into an actual WHERE clause.
  1078. $where = implode(' AND ', $where);
  1079. db_query('DELETE FROM {deploy_plan_items} WHERE ' . $where, $conditions);
  1080. }