Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Defines some macros around sf:deploy to install and uninstall packages.
<!-- TODO: Review Ant v1.8 local properties -->
<project xmlns:sf="antlib:com.salesforce">
<!-- Download from Salesforce Tools page under Setup -->
<typedef
uri="antlib:com.salesforce"
resource="com/salesforce/antlib.xml"
classpath="${basedir}/lib/ant-salesforce.jar"/>
<!-- Download from http://sourceforge.net/projects/ant-contrib/files/ant-contrib/1.0b3/ -->
<taskdef
resource="net/sf/antcontrib/antlib.xml"
classpath="${basedir}/lib/ant-contrib-1.0b3.jar"
/>
<!-- Download from https://code.google.com/p/missing-link/ -->
<taskdef
name="http"
classname="org.missinglink.ant.task.http.HttpClientTask"
classpath="${basedir}/lib/ml-ant-http-1.1.3.jar"/>
<!-- Download from http://www.oopsconsultancy.com/software/xmltask/ -->
<taskdef
name="xmltask"
classname="com.oopsconsultancy.xmltask.ant.XmlTask"
classpath="${basedir}/lib/xmltask.jar"/>
<macrodef name="installPackage" description="Installs the given managed package">
<attribute name="namespace" description="Namespace of managed package to install."/>
<attribute name="version" description="Version of managed package to install."/>
<attribute name="packagePassword" description="Password used to install the pacakge. Optional." default=""/>
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<sequential>
<!-- Generate optional <password> element? -->
<if><equals arg1="@{packagePassword}" arg2=""/>
<then><property name="passwordElement" value=""/></then>
<else><property name="passwordElement" value="&lt;password&gt;@{packagePassword}&lt;/password&gt;"/></else>
</if>
<!-- Generate working folder and metadata files representing the package to install -->
<delete dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy/installedPackages"/>
<echo file="${basedir}/installdeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>@{namespace}</members><name>InstalledPackage</name></types><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/installedPackages/@{namespace}.installedPackage"><![CDATA[<InstalledPackage xmlns="http://soap.sforce.com/2006/04/metadata"><versionNumber>@{version}</versionNumber>${passwordElement}</InstalledPackage>]]></echo>
<sf:deploy deployRoot="${basedir}/installdeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<macrodef name="uninstallPackage" description="Uninstalls the given managed package">
<attribute name="namespace" description="Namespace of managed package to install."/>
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<sequential>
<!-- Generate working folder and metadata files representing the package to uninstall -->
<delete dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy"/>
<mkdir dir="${basedir}/installdeploy/installedPackages"/>
<echo file="${basedir}/installdeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/destructiveChanges.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>@{namespace}</members><name>InstalledPackage</name></types><version>28.0</version></Package>]]></echo>
<echo file="${basedir}/installdeploy/installedPackages/@{namespace}.installedPackage"><![CDATA[<InstalledPackage xmlns="http://soap.sforce.com/2006/04/metadata"><versionNumber>@{version}</versionNumber></InstalledPackage>]]></echo>
<sf:deploy deployRoot="${basedir}/installdeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<!-- Deploys the given file as a static resource via the sf:deploy Ant task (Metadata API) -->
<macrodef name="staticResource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="developername" description="Developer name for the Static Resource."/>
<attribute name="file" description="Source file for the static resource."/>
<attribute name="contenttype" description="Content type of the Static Resource."/>
<sequential>
<!-- Generate working folder and metadata files representing the package to uninstall -->
<delete dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy"/>
<mkdir dir="${basedir}/staticresourcedeploy/staticresources"/>
<echo file="${basedir}/staticresourcedeploy/package.xml"><![CDATA[<Package xmlns="http://soap.sforce.com/2006/04/metadata"><types><members>*</members><name>StaticResource</name></types><version>29.0</version></Package>]]></echo>
<copy file="@{file}" tofile="${basedir}/staticresourcedeploy/staticresources/@{developername}.resource"/>
<echo file="${basedir}/staticresourcedeploy/staticresources/@{developername}.resource-meta.xml"><![CDATA[<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"><cacheControl>Private</cacheControl><contentType>@{contentType}</contentType></StaticResource>]]></echo>
<sf:deploy deployRoot="${basedir}/staticresourcedeploy" username="@{username}" password="@{password}"/>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce Tooling REST API ExecuteAnnoynmous resource -->
<macrodef name="executeApex" description="Provides access to the Salesforce Tooling REST API ExecuteAnnoynmous resource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="resultprefix" description="Property name prefix used for properties containing response data" default="executeAnonymousResponse"/>
<attribute name="failonerror" description="If the execute fails then fail the Ant script" default="true"/>
<text name="apexcode"/>
<sequential>
<!-- Login -->
<login username="@{username}" password="@{password}" serverurl="serverUrl" sessionId="sessionId"/>
<!-- Execute Apex via Tooling API /executeAnonymous resource -->
<http url="https://na11.salesforce.com/services/data/v29.0/tooling/executeAnonymous" method="GET" entityProperty="executeAnonymousResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<query>
<parameter name="anonymousBody" value="@{apexcode}"/>
</query>
</http>
<!-- Parse JSON response and set properites -->
<script language="javascript">
var response = eval('('+project.getProperty('executeAnonymousResponse')+')');
for(field in response)
project.setProperty('@{resultprefix}.' + field, response[field]);
</script>
<!-- Fail on error?-->
<if>
<and>
<equals arg1="@{failonerror}" arg2="true"/>
<equals arg1="${@{resultprefix}.success}" arg2="false"/>
</and>
<then>
<if>
<equals arg1="${@{resultprefix}.compiled}" arg2="false"/>
<then>
<fail message="${@{resultprefix}.line}:${@{resultprefix}.column} ${@{resultprefix}.compileProblem}"/>
</then>
<else>
<fail message="${@{resultprefix}.exceptionMessage} ${@{resultprefix}.exceptionStackTrace}"/>
</else>
</if>
</then>
</if>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce Tooling REST API StaticResource resource -->
<macrodef name="staticResource.toolingapi" description="Provides access to the Salesforce Tooling REST API StaticResource resource">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="developername" description="Developer name for the Static Resource."/>
<attribute name="body" description="Base64 encoded data for the Static Resource."/>
<attribute name="contenttype" description="Content type of the Static Resource."/>
<sequential>
<!-- Login -->
<login username="@{username}" password="@{password}" serverurl="serverUrl" sessionId="sessionId"/>
<!-- Create the StaticResource (later versions of this macro can support other operations) -->
<http url="https://na11.salesforce.com/services/data/v29.0/tooling/sobjects/StaticResource/" printresponse="true" printrequest="true" expected="201" method="POST" entityProperty="executeAnonymousResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Content-Type" value="application/json"/>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<entity>
{
"Name": "@{developerName}",
"Body": "@{body}",
"ContentType" : "@{contentType}"
}
</entity>
</http>
</sequential>
</macrodef>
<!-- Login into Salesforce and return the session Id and serverUrl -->
<macrodef name="login">
<attribute name="username" description="Salesforce user name."/>
<attribute name="password" description="Salesforce password."/>
<attribute name="serverurl" description="Server Url property."/>
<attribute name="sessionId" description="Session Id property."/>
<sequential>
<!-- Obtain Session Id via Login SOAP service -->
<http url="https://login.salesforce.com/services/Soap/c/29.0" method="POST" failonunexpected="false" entityProperty="loginResponse" statusProperty="loginResponseStatus">
<headers>
<header name="Content-Type" value="text/xml"/>
<header name="SOAPAction" value="login"/>
</headers>
<entity>
<![CDATA[
<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>@{username}</sf:username>
<sf:password>@{password}</sf:password>
</sf:login>
</env:Body>
</env:Envelope>
]]>
</entity>
</http>
<!-- Parse response -->
<xmltask destbuffer="loginResponseBuffer">
<insert path="/">${loginResponse}</insert>
</xmltask>
<if>
<!-- Success? -->
<equals arg1="${loginResponseStatus}" arg2="200"/>
<then>
<!-- Parse sessionId and serverUrl -->
<xmltask sourcebuffer="loginResponseBuffer" failWithoutMatch="true">
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/:loginResponse/:result/:sessionId/text()" property="@{sessionId}"/>
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/:loginResponse/:result/:serverUrl/text()" property="@{serverUrl}"/>
</xmltask>
</then>
<else>
<!-- Parse login error message and fail build -->
<xmltask sourcebuffer="loginResponseBuffer" failWithoutMatch="true">
<copy path="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='Fault']/*[local-name()='faultstring']/text()" property="faultString"/>
</xmltask>
<fail message="${faultString}"/>
</else>
</if>
</sequential>
</macrodef>
<!-- Base64 encode a files data -->
<macrodef name="base64encode">
<attribute name="file" description="File to base 64 encode."/>
<attribute name="base64" description="Property to store the base 64 encoded data."/>
<sequential>
<loadfile property="filedata" srcFile="@{file}"/>
<script language="javascript">
importClass(javax.xml.bind.DatatypeConverter);
project.setProperty('@{base64}',
DatatypeConverter.printBase64Binary(
new java.lang.String(project.getProperty('filedata')).getBytes("UTF-8")));
</script>
</sequential>
</macrodef>
<!-- Provides access to the Salesforce REST API for a SOQL query -->
<macrodef name="runQuery" description="Run database query">
<attribute name="sessionId" description="Salesforce user name."/>
<attribute name="serverUrl" description="Salesforce url."/>
<attribute name="query" description="Salesforce password."/>
<attribute name="queryResult" description="Query result property name"/>
<sequential>
<!-- Extract host/instance name from the serverUrl returned from the login response -->
<propertyregex property="host"
input="${serverUrl}"
regexp="^((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$"
select="\3"
casesensitive="false" />
<!-- Execute Apex via REST API /query resource -->
<http url="https://${host}/services/data/v29.0/query" method="GET" entityProperty="queryResultResponse" statusProperty="loginResponseStatus" printrequestheaders="false" printresponseheaders="false">
<headers>
<header name="Authorization" value="Bearer ${sessionId}"/>
</headers>
<query>
<parameter name="q" value="@{query}"/>
</query>
</http>
<property name="@{queryResult}" value="${queryResultResponse}"/>
</sequential>
</macrodef>
</project>
@ghost

This comment has been minimized.

Copy link

ghost commented Dec 15, 2014

Is it possible to turn ${serverUrl} into ${instance} on line 99 and line 145?

<propertyregex property="instance" input="${serverUrl}" regexp="(https://[^/]+)/" select="\1" casesensitive="false" />
<http url="${instance}/services/data/v32.0/tooling/executeAnonymous" />
...
@chiragmehta84

This comment has been minimized.

Copy link

chiragmehta84 commented Apr 6, 2015

Is it possible to use sessionid only for deployment ???

<sf:deploy deployRoot="${basedir}/installdeploy" sessionId ="@{sessionId}"/>
@chiragmehta84

This comment has been minimized.

Copy link

chiragmehta84 commented Apr 6, 2015

Ye sessionId does work in sf:deploy. However if providing a session id. 'serverurl' attribute should be a specific app server instance (e.g. na1, na2) and can't be login.salesforce.com or test.salesforce.com, it needs to be na14.salesforce.com or so.

@afawcett

This comment has been minimized.

Copy link
Owner Author

afawcett commented Apr 7, 2015

Thats really interesting, i didn't know sf:deploy supported sessionId cool!

@afawcett

This comment has been minimized.

Copy link
Owner Author

afawcett commented Apr 7, 2015

Though i don't see any reference in the Ant migration tool docs?

@chiragmehta84

This comment has been minimized.

Copy link

chiragmehta84 commented May 21, 2015

I tried it and I'm using it too, it works like a charm :)

<sf:deploy serverurl="${sf.destinationserverurl}" sessionId="${sf.destinationsessionId}" maxPoll="${sf.maxPoll}" deployRoot="exported_foldername" rollbackOnError="true"/>
@arangari

This comment has been minimized.

Copy link

arangari commented Oct 5, 2015

Hello! Thanks for this script.

i am using this script for deploying dependency tree before start working. however I am not able to define that the dependent package should be deployed for everyone, instead of only single user.

how would i do that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.