Title: Client Shell API Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Introduction ==================== To script the interaction with ACE, you can use its shell based client API. Typical use cases include the tight integration of ACE into your development or automated build process, the creation of a custom user interface, or interactive use for those who prefer a shell over a GUI. The shell commands extend the standardized OSGi shell, using the GoGo implementation from Apache Felix. This page does not aim to give an overview of all commands available in that shell, so please consult the documentation of the GoGo shell itself in case you're not familiar with it. Overview ==================== Before we dive into the various shell commands, let's start with an overview of how all of this works. Everything in ACE is an entity. For example, an artifact or a feature is an entity, but also the association between an artifact and a feature is. Entities have properties and tags. Properties are the "fixed" attributes that make up a specific type of entity. For example a feature has a name and a description. If you know the type of entity, its properties are also known. Tags on the other hand are attributes that a user can freely add and refer to. Sessions and Workspaces ======================= The client, independent of whether you're using the WebUI, REST or shell, always works in the same way. You start out by creating a session or workspace that you can then start editing. Once you're happy with your changes, you commit them back to the server. Creating a session in the shell is done like this: w = (cw) The "cw" (create workspace) command checks out and returns a new workspace. We assign that result to a variable, because all our subsequent commands operate on the workspace. Optionally, when creating the workspace, we can provide several parameters: w = (cw [showunregisteredtargets=false]) w = (cw european dutch dutch) w = (cw european dutch dutch [showunregisteredtargets=false]) The three names (european dutch dutch) are names of repositories. ACE supports a flexible multi-tenancy mechanism that allows you to specify customer or tenant names for all the different repositories. In this example, we checkout a european store repository, and a dutch target and deployment repository. Most people will probably start out with a single workspace with default repositories. The map (maps are entered like this: [key1=val1 key2=val2 key3=val3]) contains an optional set of configuration properties for the workspace. At the moment, the only property there is is "showunregisteredtargets" which determines if the workspace will contain targets that have not been registered. It finds those by scanning all audit logs. By default, they are included but in some use cases you might not want them to be visible. In such cases, rather than filtering them out of your results, you can completely remove them, which saves quite a bit of processing time as well. When you're done with your workspace, you can clean it up like this: rw $w This will remove the workspace from memory. Note that any changes will be lost, unless you commit the workspace first: $w commit This is actually not a shell command we explicitly created. Instead, the shell is smart enough to scan an instance (our workspace $w) for methods and tries to invoke those. It also parses arguments as we will soon see. Let's start creating some entities: $w cf feature-base $w cd dist-core $w cf2d "(name=feature-base)" "(name=dist-core)" This gives us a feature, a distribution and the association between the two. Associations have a left and right hand side, and both are OSGi filters. This means you can create quite complex associations, but in this case we use a simple condition that matches only a single feature and distribution. If you want to see what you've created, you can list all entities of a certain type like this: $w lf $w ld $w lf2d Lists all features, distributions and feature to distribution associations. These list commands accept a filter condition as well: $w lf "(name=feature-*)" Lists all features whose name start with "feature-". Let's proceed by adding a bundle. Bundles are a specific type of artifact and there are several ways to add them. The easiest one, most of the time, is: $w ca file:///path/to/some/bundle.jar true This will create an artifact, using the URL you specified. Because the second parameter was set to true, it will upload the artifact from that URL to the OBR. It will also extract all necessary metadata from the artifact: it recognizes the type and then uses type specific extraction mechanisms. If you really want to, you can also create the bundle yourself and specify all the metadata manually: $w ca [mimetype=application/vnd.osgi.bundle artifactName=org.foo Bundle-Name=org.foo Bundle-SymbolicName=org.foo Bundle-Version=1.0.0 url=http://localhost:8080/obr/org/foo/org.foo-1.0.0.jar processorPid=] $w ca2f "(Bundle-SymbolicName=org.foo)" "(name=feature-base)" This creates the bundle. The "url" is an important attribute as it tells ACE where to get the bundle from. In this example we assume it can already be found in the OBR at that location. We also create an association. On the bundle side, we use a filter that only specifies the symbolic name of the bundle. So what happens if there is more than one version of such a bundle available? To understand that, we need to explain a few more things about associations. First of all, assocations have a cardinality for both sides. If you don't specify the cardinality, it defaults to "1:1". So what happens if you specify a filter that returns more than one entity? Two things. First of all, the list of entities is sorted in an order that is specific to the type of entity. Bundles are sorted by version, highest first. Finally, the cardinality is used to determine how many entities from the list to return. So in this case, from all the bundles with the same symbolic name, the bundle with the highest version is selected. You can use this to create features that automatically update to the highest version of a bundle, or if you're more specific, are fixed to a version or version range. Combined with semantic versioning this gives you a powerful way to for example create a feature where bugfixes or backward compatible changes will automatically be upgraded, but major changes require human intervention. Let's proceed to create a target: $w ct target-1 $w cd2t "(name=dist-core)" "(id=target-1)" This creates the target and associates it with the "dist-core" distribution. Deleting entities is also quite easy. You first need to get hold of the entity you want to delete though, which can be done like this: targetlist = ($w lt "(id=target-1)") This gives you a list of targets, but since the filter is very specific, the list will only contain one entity. The shell now has a helper command to fetch the first item from the list: t1 = (first $targetlist) $w dt $t1 Fetches the first and only target in the list and assigns it to a variable called "t1". Subsequently deletes the target from the workspace. Deleting other entities is done in a similar way.