Tuesday 19 December 2017

SFDX and the Metadata API Part 2 - Scripting

SFDX and the Metadata API Part 2 - Scripting

Introduction

In Part 1 of this series, I covered how you can use the SFDX command line tool to deploy metadata to a regular (non-scratch) Salesforce org, including checking the status and receiving the results of the deployment in JSON format. In this post I’ll show how the deploy and check can be combined in a node script to allow you to show the progress of the deployment. The examples below are for MacOS - if that isn’t your operating system you may have to do some tweaking, although as I’m not using directory names I don’t think that will be the case. These examples also assume that you are in the directory that you cloned the repository into in Part 1.

Needs Node

You’ll need Node.js installed in order to try out the example - you can download it here. You’ll also need the SFDX CLI, but I’m sure everyone has that from the first post in this series, right?

Node has a built-in module, named child_process, that allows you to execute an application on your local disk.  While most things Node and JavaScript are asynchronous, the authors of child_process have given us synchronous versions too. These block the node event loop until the application has finished executing and then return the output of the application as the result. Perfect for scripting.

Executing SFDX

The function that we are interested in is execFileSync, which takes the name(or path) of the application to execute and an array of parameters. In the previous post, my command to execute the deployment was:

> sfdx force:mdapi:deploy -d src -u keirbowden@googlemail.com

To carry out the same operation via the execFileSync function:

child_process.execFileSync('sfdx',
                           ['force:mdapi:deploy', '-d', 'src',
                            '-u', 'keirbowden@googlemail.com]);

Processing the Results

By default the results of the deploy will be returned in a human-readable text format, but supplying an additional parameter of ‘—json’ turns it into JSON format,  (note this is much redacted output!):

{
  "status": 0,
  "result": {
      ...
    "id": "0Af80000003ynf6CAA",
    "status": "Succeeded",
    "success": true
  }
}

which JavaScript can easily parse into an object - this is the main reason that I script tools like this in node - parsing JSON in bash scripts is way more difficult.

var jsonResult=child_process.execFileSync('sfdx',
                               ['force:mdapi:deploy', '-d', 'src',
                                '-u', 'keirbowden@googlemail.com]);
var result=JSON.parse(jsonResult);
console.log(‘Status = ‘ + result.result.status);

Outputs ‘Status = Succeeded’.

Polling the Deployment

The result from the execution of the deployment contains an id parameter:

    "id": "0Af80000003ynf6CAA"

which can be used to request a deployment report from the org. 

child_process.execFileSync('sfdx’,
                           ['force:mdapi:deploy:report',
                           '-i', result.result.id,
                            '-u', ‘keirbowden@googlemail.com', '--json’]);

the results of which are again returned in JSON format and can be processed easily through JavaScript.

All together now

Based on the above learning, here’s the sample node script that executes a deployment and then polls the org until it has completed, successfully or otherwise:

#!/usr/local/bin/node

var child_process=require('child_process');
var username='keirbowden@googlemail.com';

var deployParams=['force:mdapi:deploy', '-d', 'src',
                  '-u', username, '--json'];

var resultJSON=child_process.execFileSync('sfdx', deployParams);
var result=JSON.parse(resultJSON);
var status=result.result.status; while (-1==(['Succeeded', 'Canceled', 'Failed'].indexOf(status))) { var msg='Deployment ' + status; if ('Queued'!=status) { msg+=' (' + result.result.numberComponentsDeployed + '/' + result.result.numberComponentsTotal + ')' } console.log(msg); var reportParams=['force:mdapi:deploy:report', '-i', result.result.id, '-u', username, '--json']; resultJSON=child_process.execFileSync('sfdx', reportParams); result=JSON.parse(resultJSON); status=result.result.status; } console.log('Deployment ' + result.result.status);

Breaking this up a little, the child_process module is included and the name of my user assigned to a variable as I’ll be using it it in a few places:

var child_process=require('child_process');
var username='keirbowden@googlemail.com';

The deployment is then executed and the results parsed:

var deployParams=['force:mdapi:deploy', '-d', 'src',
                  '-u', username, '--json'];

var resultJSON=child_process.execFileSync('sfdx', deployParams);
var result=JSON.parse(resultJSON);

Then the code enters a loop that continues until the deployment has completed:

var status=result.result.status;
while (-1==(['Succeeded', 'Canceled', 'Failed'].indexOf(status))) {

Next I generate a message to display to the user - the status of the deployment and, if it isn’t queued, the number of components deployed and the total number:

var msg='Deployment ' + status;
if ('Queued'!=status) {
    msg+=' (' + result.result.numberComponentsDeployed + '/' +
                result.result.numberComponentsTotal + ')'
}
console.log(msg);

Then I execute the report on the status of the deployment and assign the results to the existing variable - as far as I’ve been able to tell the results of the deploy and deploy report are the same, but this doesn’t appear to be documented anywhere so may be subject to change:

var reportParams=['force:mdapi:deploy:report', '-i', result.result.id,
                          '-u', username, '--json'];
        resultJSON=child_process.execFileSync('sfdx', reportParams);
        result=JSON.parse(resultJSON);
        status=result.result.status;

and round the loop it goes again.

Executing this script generates the following output:

> node deploy.js
Deployment Queued
Deployment Pending (0/0)
Deployment Pending (0/0)
Deployment Pending (0/0)
Deployment InProgress (0/1)
Deployment Succeeded

Conclusion

This script just scratches the surface of what can be done with the results - for example, if any of the components fail it can raise the alarm in the org or elsewhere. It also polls continuously, which I wouldn’t recommend for a large number of components - I typically sleep for a few seconds in between each request. It also doesn’t do much with the final result aside from writing it to the screen. 

The SFDX force:mdapi:deploy command does have a ‘-w’ option to wait a specified period of time for the deployment to complete, which if set to -1 reports the progress at regular intervals in a very similar way until the deployment completes. This is fine if you are happy to wait until the end before taking any further action, but I like this granularity so that I can take action as soon as something happens. You should use what works for you.

Related Posts

 

1 comment: