Relatively Advanced Content Deployment with Deploy Module

In a previous post I announced a new module I had contributed back to Drupal.org, Incremental Deploy, which extends Deploy module in various ways, one of which is to make deployable certain items that couldn't previously be deployed. This follow-up post discusses some of the issues you can come up against when deploying certain types of content under certain circumstances and also what is involved in making something deployable. It assumes prior knowledge of Drupal's API hooks and functions and of the basic usage of Deploy module but not of its inner workings.

What's in a node?

As a starting point, let's look at what happens when you deploy a node from a source environment to a target environment. In a nutshell, here's how it works:

You send a representation of the node over xmlrpc to be processed by a service on a remote site. The xmlrpc call specifies the 'node.Save' method, and on the remote site the module responsible for the node.Save method (which is node_service module, part of the Services package) takes the node and passes it to the callback function it has associated with that method. The callback function saves the node.

Now, you might assume that what is happening here is that the node gets passed as a regular, fully-loaded node object and that it is simply passed to node_save() on the other end. But the essential thing is, what happens on the other end must replicate exactly what would have happened had the user created or edited the node directly in that environment. Think of the number of times you've hook_form_alter'd a node form; if there's no form involved, none of that code gets executed, and this could lead to discrepancies between the source and target environments.

So, what node_service module actually does when it receives a node from an xmlrpc call is, it simulates submission of the node form using drupal_execute. The node, rather than being sent as a regular node object, is sent as a set of form values ready to be passed to drupal_execute() on the other end. This is all explained very clearly in the excellent comments within the deploy hook in node_deploy module.

So why might this be a problem? Well, because it means we actually need to care about UI-level finnicky form widgets when all we're trying to do is save some content to the database programmatically.

Here are the steps involved in a node being created in a source environment and it being deployed to the target environment:

  1. User fills out node creation form and hits submit, node gets saved on the source.
  2. User goes to deploy the node, filling in credentials for the remote environment.
  3. Once deployment has been initiated, Deploy module checks for any dependencies, e.g. node references or files.
  4. Deploy module calls the deploy hook of node_deploy module, which is responsible for deploying this type of entity.
  5. The node is converted into a set of form values and sent, via xmlrpc, to the remote environment, specifying the node.Save() method to process it.
  6. The node_service_save() function receives the node, prepares the node form, and drupal_executes it with the values it has received.
  7. If all went well, it returns the nid of the newly created node, otherwise it returns an error, which will abort the rest of the deployment.

What could possibly go wrong?

For the most part, this will be a problem-free process - your deployment logs will show a "success" result for every item - but there are some not too off the wall scenarios where you'll see various errors being returned, or worse - silent failures.

Scenario #1

You're using a custom CCK field which includes a filefield, but nodes of this type get deployed without their files. See "How do you make something deployable?" below.

Scenario #2

Your node contains a CCK date field that uses the date popup widget. Unless you have applied the patch at http://drupal.org/node/945526, you'll only be able to deploy date field values if you're using the select lists widget for your date fields.

Scenario #3

You're using upload module to add file attachments to nodes, they seem to deploy fine but just don't show up on the remote site. This is the one scenario where you'll get an extremely frustrating silent failure - the deployment of the node returns success but the node has not been saved on the remote site. This is due to a conflict with how upload module alters the node form and the solution is simply not to use core upload module for file attachments but to use a CCK filefield instead (see http://drupal.org/node/459192)

Scenario #4

You have a multi-language site and are using i18n module to enhance Drupal's localization features. This in itself is not a problem but problems can indeed arise if, under Administer » Site configuration » Languages » Multilingual System, you have your content selection mode set to "Current language and language neutral". Imagine a German content producer, creating German content of a node type that has a nodereference field. The current language is set to German, which makes it easy for the producer to select from German nodes for the nodereference field. Once created, the node gets scheduled for deployment to the QA server, along with a whole slew of other content in various languages. The adminstrator doing the deployment enters the credentials for the remote environment, but the language for the user she's logging in as (a generic "Deploy" user) is set to en-US. Now, when the node form for the node in question is being prepared by node_service_save() module, a completely different set of referencable nodes are returned for the nodereference field (because they are being filtered by language=en-US, not by language="de"), and the one that's been passed is not among them. Fail. The whole deployment breaks. There is a workaround for this and I will gladly post it if anyone has actually read through this scenario and found that they've experienced the same problem. I am starting to feel like I'm going into too much detail about very edge-casey issues that nobody else will even come across.

How do you make something deployable?

If we're talking about a node that's just not fully deployable, e.g. because it has a custom CCK field with a nodereference field or a filefield, there are two main steps:

  1. Implement hook_node_deploy_check() to make sure the referenced node or file will get added to the deployment plan as a dependency of your node.
  2. Implement hook_node_deploy() to make sure the referenced node or file gets its nid or fid swapped out for its correct value in the remote environment.

For entirely custom entities that are the responsibility of your custom module, your module needs to implement hook_deploy() which should package up the information as necessary for sending along to the remote site. Then you'll need to add a service module for receiving it on the other end. Of course you'll also need some way of adding it to a deployment plan. There are plenty of examples of all of this in Incremental Deploy module and its submodules.

Fantastic initiative

I'm so happy to see you working on this tremendously important territory. Thanks for the great work, and for letting us know about it.