First steps with Apache ACE

Introduction

"Apache ACE is a software distribution framework that allows you to centrally manage and distribute software components, configuration data and other artifacts to target systems." (from ace.apache.org)
Well, that sounds good enough to try it out, at least for me.
I like the idea to centrally configure deployments with different version and have a way to automatically distribute those.

Starting ACE

Setting up Apache ACE was pretty easy. The Getting started guide contains all the necessary steps.
My MacBook was the ACE server and my Raspberry Pi a target.

Using the Web GUI is easy and straightforward (nice one guys ;-) ).
But that's for little children. I wanna find a way to automate everything with scripts.

The Client Shell API

Basically there are two ways to talk to the server remotely. One is the Client Shell API and the other way is via REST API. For now I'll stick with the Shell API

1st step: connecting to the server as shell client

Before we can write a script we have to connect to the server.
With some help from the iQSpot people (see here) I figured it out.
They suggest starting the client like this:

java -Dagent.discovery.serverurls="http://_server_:_port_"  
     -Dorg.apache.ace.server="_server_:_port_"  
     -Dorg.apache.ace.obr="_server_:_port_"  
     -Dorg.osgi.service.http.port=-1  
     -jar client.jar

Unfortunately, that didn't work for me (even after adding some missing backslashes).
The default is that you should be in the directory of client.jar. "-jar client.jar" wasn't the problem.
The startup searches for the client/conf directory, so when you see this exception:

java.lang.IllegalArgumentException: Bad arguments; either not an existing directory or an invalid interval.  
    at org.apache.ace.configurator.Configurator.<init>(Configurator.java:89)  
    at org.apache.ace.configurator.Activator.init(Activator.java:33)  
    at org.apache.felix.dm.DependencyActivatorBase.start(DependencyActivatorBase.java:76)  
    at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645)  
    at org.apache.felix.framework.Felix.activateBundle(Felix.java:2146)  
    at org.apache.felix.framework.Felix.startBundle(Felix.java:2064)  
    at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1291)  
    at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:304)  
    at java.lang.Thread.run(Thread.java:722)

You're probably starting the client from a different directory.
To get rid of that exception we have to set a property:
\-Dorg.apache.ace.configurator.CONFIG\_DIR=

All in all the command to start the client looks like this:

java -Dagent.discovery.serverurls="http://_server_:_port_"\\  
     -Dorg.apache.ace.server="_server_:_port_"\\  
     -Dorg.apache.ace.obr="_server_:_port_"\\  
     -Dorg.osgi.service.http.port=-1\\  
     -Dorg.apache.ace.configurator.CONFIG\_DIR="apache-ace-2.0.1-bin/client/conf"\\  
     -jar apache-ace-2.0.1-bin/client/client.jar

Now we can start the client.
But to pass a script to the shell we need two more arguments. Thanks again to the iQSpot people. They already pointed out those arguments:

*   \-Dgosh.args="–\-args"
*   \-Dace.gogo.script.delay=_delay_
*   \-Dace.gogo.script=/path/to/script.gogo

What do those three do?
Everything you'll pass as "gosh.args" will be executed immidiately. If pass "--help" for example and start the client you'll see the help output.
The delay is helpful when you want to give your client some time to synchronize with the server.
"ace.gogo.script" should be obvious ;-)
We end up with the following command:

java -Dagent.discovery.serverurls="http://_server_:_port_"\\  
     -Dorg.apache.ace.server="_server_:_port_"\\  
     -Dorg.apache.ace.obr="_server_:_port_"\\  
     -Dorg.osgi.service.http.port=-1\\  
     -Dorg.apache.ace.configurator.CONFIG\_DIR="apache-ace-2.0.1-bin/client/conf"\\  
     -Dace.gogo.script.delay="3000"\\  
     -Dace.gogo.script="script.foo"\\  
     -jar apache-ace-2.0.1-bin/client/client.jar

Now we have to find out, what we should put into "script.foo".

Shell commands

Every (basic) command is described here.
The steps are quite simple: cw, ca, cf, ca2f, cd, cf2d
If you don't like or get the abbreviations (it took me a while) there is also a nice picture in the REST API documentation:

ACE

What the picture is missing is cw or "create workspace". When using the Shell API you need a workspace, which you can commit later.

The script

So, what should skript.foo do? Let's assume we have to upload some generated artifacts from our CI server
The steps are

  1. create workspace
  2. add the new Jars as artifacts from certain directory
  3. create a new feature
  4. add artifacts to feature
  5. create a new distribution
  6. add new feature and existing ones to distribution
  7. add feature to existing target

I have to admit that it took me quite a while to figure everything out because I'm not very experienced with Apache Felix GoGo.
Creating the workspace is easy: w = (cw)
Now we can call the workspace with $w. Adding the Jars was more difficult. Let's assume the directory is ./toAdd. Then the command looks like this:

each (\[(ls toAdd)\]) {$w ca (($it toURL) toString) false}

You "toAdd" can be changed to any path and you could use some wildcards, like ls toAdd/\*.jar I guess if you're used to GoGo the command won't be a surprise. If you're not used to it, I would like to explain the different parts to you:
each takes a list and a function. ls toAdd returns a File array. That's why we need the brackets. They convert the array into a list. After that comes the function, indicated by the braces.
$w ca is the method to create an arfifact. $it is the iterator over the list that is provided by each.
Then we call the methods toURL and toString because reflection makes it possible ;-)

Third step: add all artifacts to feature

each ($w la "(Bundle-SymbolicName=org.camunda.\*)") {symbolicName=($it getAttribute "Bundle-SymbolicName"); $w ca2f "(Bundle-SymbolicName="$symbolicName")" "(name=test-feature)"}

Again, we use a for-each-loop.
$w la lists all the bundles that match the passed pattern. (Here, I want to add all camunda bundles, no advertisement ;-))
Then I save the symbolic name in a variable, so it's easier for me later to reference it.
org.apache.ace.client.repository.RepositoryObject has a getAttribute method, which we use here.
Also, please note the semicolon.
We use the symbolic name as part of the first argument for ca2f (create artifact2feature).
The String contains three parts

*   "(Bundle-SymbolicName="
*    $symbolicName
*   ")"

I don't know why, but we don't ne a "+" for string concatenation. The second argument is the name of the feature. I just assume it to stay the same: "test-feature"
Creating a distribution and a feature2distribution are nothing special.

All in all we end up with the following:

w = (ace:cw)  
$w cf test-feature  
$w cd test-distro  

each (\[(ls toAdd)\]) {$w ca (($it toURL) toString) false}  

each ($w la "(Bundle-SymbolicName=org.camunda.\*)") {symbolicName=($it getAttribute "Bundle-SymbolicName"); $w ca2f "(Bundle-SymbolicName="$symbolicName")" "(name=test-feature)"}  

$w cf2d "(name=test-feature)" "(name=test-distro)"  

$w commit

That should do the trick so far.
Stay tuned for my next steps with ACE ;-)

Did you find this article valuable?

Support Ronny Bräunlich by becoming a sponsor. Any amount is appreciated!