12

We are in the process of setting up automatic build using Ant. We want to do some start up activities like read an xml file which is stored as a static resource and verify it with a custom object data to make sure some settings are valid. We want the build to fail if there is a mismatch on any of the data. So to do this, i am trying to create an ant task where i can create a target which would do the following? a. upload static resources which are xml files from a folder. b. execute an apex class which would compare the xml file with a custom object data and if there is a mismatch fail the target build. Do you guys know how to have an ant target which would execute an apex class or even a visual force page and spit an error please? Thanks Buyan

15

Inspired by bigassforce's answer, my preference for platform neutral Ant scripts and wanting to see if I could get the Tooling API working from Ant, I've worked up this solution. Its taken quite a while actually, but I've enjoyed it and it has shown some great possibilities for calling Salesforce REST API's from Ant. Anyway, first the data we want to assert exists in the target org, contained in assertdata.xml.

<accounts>
    <name>Burlington Textiles Corp of America</name>
    <name>Dickenson plc</name>
    <name>Edge Communications</name>
</accounts>

The following is a demo Ant script to upload that as a StaticResource and Run some Apex to check it against the database, in this case the Account object. The script makes use of the Ant macro functionality to hide the complex bits away. I've uploaded the ant-salesforce.xml macro file here, it has additional Ant library download requirements to support the HTTP callouts see comments within.

<project name="demo" default="assertOrgData" basedir="." xmlns:sf="antlib:com.salesforce">

    <property file="${basedir}/build.properties"/>

    <target name="assertOrgData">

        <!-- Import macros -->
        <import file="${basedir}/lib/ant-salesforce.xml"/>

        <!-- Deploy the Static Resource -->
        <staticResource
            username="${sf.username}"
            password="${sf.password}"
            developername="assertdata"
            file="${basedir}/assertdata.xml"
            contenttype="application/xml"/>

        <!-- Execute the following Apex code -->
        <executeApex
            username="${sf.username}"
            password="${sf.password}">
            <![CDATA[

                Dom.Document doc = new Dom.Document();
                doc.load(new PageReference('/resource/assertdata').getContent().toString());
                List<String> accountNames = new List<String>();
                for(Dom.XMLNode accountNode : doc.getRootElement().getChildElements())
                    accountNames.add(accountNode.getText());
                List<Account> accountRecords = [select Id from Account where Name in :accountNames];            
                System.assertEquals(accountNames.size(), accountRecords.size());

            ]]>
        </executeApex>      

    </target>

</build>

NOTE: To fail the build throw an exception or as in the case above use the System.assert functions (which actually work outside of a test context) from the Apex code (which can call other Apex code in the org as well). I used the assert approach in this demo as they felt more appropriate given the nature of the code. However you can just as easily throw an exception, that will also stop the build.

The build output for a failure will be something like this...

BUILD FAILED
/ant-salesforce-demo/build.xml:19: The following error occurred while executing this line:
/ant-salesforce-demo/lib/ant-salesforce.xml:125: System.AssertException: Assertion Failed: Expected: 3, Actual: 2 AnonymousBlock: line 9, column 1

TOOLING API USAGE: The execute apex macro uses the recently released execute Apex feature of the Tooling API. You will also find in the macro file an example of uploading the StaticResource via the Tooling API in the macro called staticResource.toolingapi, which turned out to be much much faster! However behaviour wise it would not overwrite the file if it was already deployed. So I moved it aside and wrote an Metadata API (sf:deploy) based one instead for now.

| improve this answer | |
  • Thanks for your detailed solution on using the tooling api. 1. Since we would execute the build inside our company firewall, is the tooling api call a rest based call or what port should we open to execute the build?2. Can i also save the build in a custom object inside the org which i assume I can ? Thanks a lot for your answer. – buyan kumar Nov 3 '13 at 12:54
  • @user320 Thanks for your great solution. I liked the curl solution which is light weight but i think the tooling api call provides more control on to execute custom apex. I really appreciate your effort in documenting the task and explaining it to me.. – buyan kumar Nov 3 '13 at 12:59
  • Yep its a REST API, and yep if you study the macros you will start to see how to use the regular Salesforce REST API's, the login macro does a lot of the initial hard work, the rest is just knowing the REST URL, what to pass in and what to expect in response, the Salesforce REST API docs provide this quite clearly. You can also check out the excellent ApiGee Salesforce console to test stuff. – Andrew Fawcett Nov 3 '13 at 13:07
7

Your first requirement (uploading static resources) is all within the capability of the Ant Tool:

In your build.xml use the deploy command:

<project default="usage" basedir="." xmlns:sf="antlib:com.salesforce">
    <taskdef uri="antlib:com.salesforce" classpath="ant-salesforce.jar" />
    <target name="push" description="Uploads your static resources">
        <sf:deploy username="user_here" password="pass_here" serverurl="https://login.salesforce.com" deployRoot="src"/>
    </target>
</project>

You will want a package.xml file like this:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Static Resources for automatic build</fullName>
    <types>
        <members>myFile1_xml</members>
        <members>myFile2_xml</members>
        <name>StaticResource</name>
    </types>
    <version>29.0</version>
</Package>

with your directory structure containing all the static resources you want to upload:

  • src
    • staticresources
      • myFile1_xml.resource
      • myFile1_xml.resource-meta.xml
      • myFile2_xml.resource
      • myFile2_xml.resource-meta.xml
    • package.xml
  • build.xml

Your second requirement (executing apex code) could use CURL to run the SOAP API:

<project default="usage" basedir="." xmlns:sf="antlib:com.salesforce">
    <taskdef uri="antlib:com.salesforce" classpath="ant-salesforce.jar" />
    <target name="exec" description="Runs Apex using executeAnonymous">
        <exec executable="curl"><arg line="https://login.salesforce.com/services/Soap/c/29.0 - -header 'Content-type: text/xml' --header 'SOAPAction: login' --data @login.xml" /></exec>
        <exec executable="curl"><arg line="https://your_instance.salesforce.com/services/Soap/s/29.0/your_org_id - -header 'Content-type: text/xml' --header 'SOAPAction: exec' --data @exec.xml" /></exec>
    </target>
</project>

Where login.xml looks like:

<?xml version='1.0' encoding='utf-8' ?>
<env:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
    <env:Body>
        <sf:login xmlns:sf='urn:enterprise.soap.sforce.com'>
            <sf:username>user_here</sf:username>
            <sf:password>pass_here</sf:password>
        </sf:login>
    </env:Body>
</env:Envelope>

and exec.xml looks like:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <env:Header>
        <SessionHeader xmlns="http://soap.sforce.com/2006/08/apex">
            <sessionId>session_id_here</sessionId>
        </SessionHeader>
        <CallOptions xmlns="http://soap.sforce.com/2006/08/apex">
            <defaultNamespace xsi:nil="true" />
            <clientLog xsi:nil="true" />
            <debugExceptions>true</debugExceptions>
            <platform xsi:nil="true" /><remoteApplication xsi:nil="true" />
        </CallOptions>
    </env:Header>
    <env:Body>
        <sf:executeAnonymous xmlns:sf="http://soap.sforce.com/2006/08/apex">
            <sf:string>
                System.debug('herp derp');
            </sf:string>
        </sf:executeAnonymous>
    </env:Body>
</env:Envelope>

In between the two CURL exec calls you will need to do a little bit of work to get the Session ID out of the login response and into the executeAnonymous request ;-)

| improve this answer | |
  • 1
    Very nice. I was going to suggest writing a new task based on the com.salesforce package, probably by just importing a couple WSDL files, creating some source code, and building a jar from it, but I like the idea of just using CURL and a little bit of scripting. – sfdcfox Nov 1 '13 at 21:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.